Due to update 2.4.3, some mods may no longer function. FixedConfig may be necessary.
Decompiled source of BoplAbilityBlacklist v0.1.4
BoplAbilityBlacklist.dll
Decompiled a month agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.Utilities; using UnityEngine.SceneManagement; 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: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("BoplAbilityBlacklist")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyDescription("Blacklist abilities from random selection and mark them in the ability grid.")] [assembly: AssemblyFileVersion("0.1.4.0")] [assembly: AssemblyInformationalVersion("0.1.4+67e9ba466516150c8f52ea2ac491df916d45c3d1")] [assembly: AssemblyProduct("BoplAbilityBlacklist")] [assembly: AssemblyTitle("BoplAbilityBlacklist")] [assembly: AssemblyVersion("0.1.4.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.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 BoplAbilityBlacklist { public sealed class AbilityGridClickHandler : MonoBehaviour, IPointerClickHandler, IEventSystemHandler { public int SelectionIndex { get; set; } public AbilityGridEntry Entry { get; set; } = null; public void OnPointerClick(PointerEventData eventData) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Invalid comparison between Unknown and I4 //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Invalid comparison between Unknown and I4 AbilityGrid value = Traverse.Create((object)Entry).Field<AbilityGrid>("abilityGrid").Value; if ((Object)(object)value == (Object)null) { Plugin.Log.LogWarning((object)"Right-click toggle could not resolve the ability grid."); } else if ((int)eventData.button == 1) { ToggleBlacklist(value); } else if ((int)eventData.button == 0) { value.CloseGrid(); } } private void ToggleBlacklist(AbilityGrid abilityGrid) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) HashSet<int> disabledAbilities = Plugin.Instance.GetDisabledAbilities(); if (!disabledAbilities.Add(SelectionIndex)) { disabledAbilities.Remove(SelectionIndex); } Plugin.Instance.SaveDisabledAbilities(disabledAbilities); bool flag = disabledAbilities.Contains(SelectionIndex); Entry.SetColor(flag ? Color.red : ((Graphic)abilityGrid.borderImage).color); Plugin.Log.LogInfo((object)string.Format("{0} ability index {1}.", flag ? "Disabled" : "Enabled", SelectionIndex)); } } internal static class BlacklistState { public static HashSet<int> Parse(string raw) { HashSet<int> hashSet = new HashSet<int>(); string[] array = raw.Split(new char[3] { ',', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { string text2 = text.Trim(); if (text2.Length != 0 && int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { hashSet.Add(result); } } return hashSet; } public static string Serialize(IEnumerable<int> disabledAbilities) { return string.Join(",", disabledAbilities.OrderBy((int value) => value)); } } [BepInPlugin("com.collin.boplabilityblacklist", "Bopl Ability Blacklist", "0.1.0")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.collin.boplabilityblacklist"; public const string PluginName = "Bopl Ability Blacklist"; public const string PluginVersion = "0.1.0"; private Harmony? _harmony; internal ConfigEntry<string> DisabledAbilitiesRaw = null; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); Log.LogInfo((object)"Plugin.Awake fired."); DisabledAbilitiesRaw = ((BaseUnityPlugin)this).Config.Bind<string>("Abilities", "DisabledAbilities", string.Empty, "Comma-separated ability selection indices to exclude from random and mark in the grid."); SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony("com.collin.boplabilityblacklist"); _harmony.PatchAll(); string[] array = (from method in _harmony.GetPatchedMethods().Where(delegate(MethodBase method) { Patches patchInfo = Harmony.GetPatchInfo(method); return patchInfo != null && patchInfo.Owners.Contains("com.collin.boplabilityblacklist"); }) select method.DeclaringType?.Name + "." + method.Name into name orderby name select name).ToArray(); Log.LogInfo((object)string.Format("Patched {0} methods: {1}", array.Length, string.Join(", ", array))); Log.LogInfo((object)"Bopl Ability Blacklist 0.1.0 loaded."); } private void Start() { Log.LogInfo((object)"Plugin.Start fired."); } private void OnEnable() { Log.LogInfo((object)"Plugin.OnEnable fired."); } private void OnDisable() { Log.LogInfo((object)"Plugin.OnDisable fired."); } private void OnDestroy() { Log.LogWarning((object)"Plugin.OnDestroy fired. Keeping Harmony patches active for diagnostics."); } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) int num = Object.FindObjectsOfType<SelectAbility>().Length; int num2 = Object.FindObjectsOfType<AbilityGridEntry>().Length; int num3 = Object.FindObjectsOfType<AbilityGrid>().Length; int rootCount = ((Scene)(ref scene)).rootCount; bool flag = (Object)(object)GameObject.Find("Canvas") != (Object)null; bool flag2 = (Object)(object)GameObject.Find("Rectangle1") != (Object)null; Log.LogInfo((object)$"sceneLoaded: scene='{((Scene)(ref scene)).name}', mode={mode}, roots={rootCount}, selectAbilities={num}, abilityGrids={num3}, abilityGridEntries={num2}, canvas={flag}, rectangle1={flag2}"); } internal HashSet<int> GetDisabledAbilities() { return BlacklistState.Parse(DisabledAbilitiesRaw.Value); } internal void SaveDisabledAbilities(IEnumerable<int> disabledAbilities) { DisabledAbilitiesRaw.Value = BlacklistState.Serialize(disabledAbilities); ((BaseUnityPlugin)this).Config.Save(); } } } namespace BoplAbilityBlacklist.Patches { [HarmonyPatch(typeof(AbilityGridEntry))] internal static class AbilityGridEntryPatch { private static bool _loggedInit; [HarmonyPostfix] [HarmonyPatch("Init")] private static void InitPostfix() { if (!_loggedInit) { Plugin.Log.LogInfo((object)"AbilityGridEntry.Init patch fired."); _loggedInit = true; } } } [HarmonyPatch(typeof(AbilityGrid))] internal static class AbilityGridPatch { private static bool _loggedUpdate; private static bool _loggedControllerToggle; private static bool _loggedControllerProbe; private static bool _loggedInactiveGate; private static bool _loggedControllerDisableBlockedBySelection; private static int _controllerProbeButtonLogs; [HarmonyPostfix] [HarmonyPatch("Update")] private static void UpdatePostfix(AbilityGrid __instance) { //IL_003b: 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_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) if (!_loggedUpdate) { Plugin.Log.LogInfo((object)"AbilityGrid.Update patch fired."); _loggedUpdate = true; } if (!_loggedControllerProbe) { _loggedControllerProbe = true; string[] value = ((IEnumerable<Gamepad>)(object)Gamepad.all).Select((Gamepad g) => ((InputControl)g).displayName ?? ((InputControl)g).name).ToArray(); string[] value2 = ((IEnumerable<Joystick>)(object)Joystick.all).Select((Joystick j) => ((InputControl)j).displayName ?? ((InputControl)j).name).ToArray(); Plugin.Log.LogInfo((object)string.Format("ControllerProbe: gamepads={0} [{1}], joysticks={2} [{3}]", Gamepad.all.Count, string.Join(" | ", value), Joystick.all.Count, string.Join(" | ", value2))); } AbilityGridEntry[] value3 = Traverse.Create((object)__instance).Field<AbilityGridEntry[]>("grid").Value; if (value3 == null) { return; } HashSet<int> disabledAbilities = Plugin.Instance.GetDisabledAbilities(); for (int i = 1; i < value3.Length; i++) { AbilityGridEntry val = value3[i]; if (!((Object)(object)val == (Object)null)) { val.SetColor(disabledAbilities.Contains(i) ? Color.red : ((Graphic)__instance.borderImage).color); } } HandleControllerToggle(__instance, value3, disabledAbilities); } private static void HandleControllerToggle(AbilityGrid grid, AbilityGridEntry[] entries, HashSet<int> disabledAbilities) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_03ee: Unknown result type (might be due to invalid IL or missing references) //IL_03e7: Unknown result type (might be due to invalid IL or missing references) if (!Traverse.Create((object)grid).Field<bool>("isActive").Value && !_loggedInactiveGate) { _loggedInactiveGate = true; Plugin.Log.LogInfo((object)"ControllerProbe: AbilityGrid.isActive was false; using SelectedIcon-based gating instead."); } int value = Traverse.Create((object)grid).Method("get_SelectedIcon", Array.Empty<object>()).GetValue<int>(); if (value <= 0 || value >= entries.Length) { return; } Gamepad[] array = ((IEnumerable<Gamepad>)(object)Gamepad.all).Where((Gamepad g) => g.buttonWest.wasPressedThisFrame).ToArray(); bool value2 = Traverse.Create((object)grid).Field<bool>("usesKeyboardAndMouse").Value; Gamepad value3 = Traverse.Create((object)grid).Field<Gamepad>("gamepad").Value; if (_controllerProbeButtonLogs < 40 && array.Length != 0) { _controllerProbeButtonLogs++; string arg = string.Join(" | ", array.Select((Gamepad g) => ((InputControl)g).displayName ?? ((InputControl)g).name)); Plugin.Log.LogInfo((object)string.Format("ControllerProbe: buttonWest down from gamepads=[{0}], gridUsesKeyboardMouse={1}, gridGamepad='{2}'.", arg, value2, ((value3 != null) ? ((InputControl)value3).displayName : null) ?? ((value3 != null) ? ((InputControl)value3).name : null) ?? "null")); } bool flag = array.Length != 0; bool flag2 = false; Enumerator<Joystick> enumerator = Joystick.all.GetEnumerator(); try { while (enumerator.MoveNext()) { Joystick current = enumerator.Current; foreach (ButtonControl item in ((IEnumerable)(object)((InputDevice)current).allControls).OfType<ButtonControl>()) { if (item.wasPressedThisFrame) { if (_controllerProbeButtonLogs < 20) { _controllerProbeButtonLogs++; Plugin.Log.LogInfo((object)("ControllerProbe: joystick='" + (((InputControl)current).displayName ?? ((InputControl)current).name) + "' button='" + ((InputControl)item).name + "' pressed.")); } bool flag3; switch (((InputControl)item).name) { case "button2": case "button3": case "buttonWest": flag3 = true; break; default: flag3 = false; break; } if (flag3) { flag2 = true; } } } } } finally { ((IDisposable)enumerator).Dispose(); } if ((!flag && !flag2) || (flag && (value2 || (value3 != null && !array.Contains(value3)))) || (flag2 && value2)) { return; } bool flag4 = disabledAbilities.Contains(value); if (!flag4 && AbilitySelectionGuard.TryGetSelectionHolder(value, out string holderInfo)) { if (!_loggedControllerDisableBlockedBySelection) { _loggedControllerDisableBlockedBySelection = true; Plugin.Log.LogInfo((object)"Controller disable block fired because ability is currently selected by a player."); } Plugin.Log.LogInfo((object)$"Blocked disabling ability index {value} via controller X because it is currently selected ({holderInfo})."); return; } if (flag4) { disabledAbilities.Remove(value); } else { disabledAbilities.Add(value); } Plugin.Instance.SaveDisabledAbilities(disabledAbilities); bool flag5 = disabledAbilities.Contains(value); AbilityGridEntry val = entries[value]; if ((Object)(object)val != (Object)null) { val.SetColor(flag5 ? Color.red : ((Graphic)grid.borderImage).color); } if (!_loggedControllerToggle) { _loggedControllerToggle = true; Plugin.Log.LogInfo((object)"Controller toggle fired via X/buttonWest on active ability grid."); } Plugin.Log.LogInfo((object)string.Format("{0} ability index {1} via controller X toggle (gamepadWest={2}, joystickX={3}). disabledList=[{4}]", flag5 ? "Disabled" : "Enabled", value, flag, flag2, string.Join(",", disabledAbilities.OrderBy((int i) => i)))); } } internal static class AbilitySelectionGuard { internal static bool TryGetSelectionHolder(int abilityIndex, out string holderInfo) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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_004e: Invalid comparison between Unknown and I4 holderInfo = string.Empty; if (abilityIndex <= 0) { return false; } CharacterSelectBox[] array = Object.FindObjectsOfType<CharacterSelectBox>(); CharacterSelectBox[] array2 = array; foreach (CharacterSelectBox val in array2) { CharSelectMenu value = Traverse.Create((object)val).Field<CharSelectMenu>("menuState").Value; if ((int)value == 0) { continue; } List<SelectableMonoBehaviour> value2 = Traverse.Create((object)val).Field<List<SelectableMonoBehaviour>>("selectables").Value; if (value2 == null) { continue; } int value3 = Traverse.Create((object)val).Field<int>("RectangleIndex").Value; int num = 0; foreach (SelectableMonoBehaviour item in value2) { SelectAbility val2 = (SelectAbility)(object)((item is SelectAbility) ? item : null); if (val2 != null) { num++; int value4 = Traverse.Create((object)val2).Field<int>("selectedIndex").Value; int num2 = value4 - 1; if (num2 == abilityIndex) { holderInfo = $"playerBox={value3}, slot={num}, selectedSprite={value4}"; return true; } } } } return false; } } [HarmonyPatch(typeof(CallOnHover))] internal static class CallOnHoverPatch { private static bool _loggedClickPath; private static bool _loggedDisableBlockedBySelection; [HarmonyPrefix] [HarmonyPatch("OnPointerClick")] private static bool OnPointerClickPrefix(CallOnHover __instance, PointerEventData eventData) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Invalid comparison between Unknown and I4 //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) if (!_loggedClickPath) { _loggedClickPath = true; Plugin.Log.LogInfo((object)"CallOnHover.OnPointerClick patch fired."); } AbilityGridEntry val = ((Component)__instance).GetComponent<AbilityGridEntry>() ?? ((Component)__instance).GetComponentInParent<AbilityGridEntry>(); if ((Object)(object)val == (Object)null) { return true; } int value = Traverse.Create((object)val).Field<int>("index").Value; AbilityGrid value2 = Traverse.Create((object)val).Field<AbilityGrid>("abilityGrid").Value; HashSet<int> disabledAbilities = Plugin.Instance.GetDisabledAbilities(); if (eventData == null || (int)eventData.button != 1) { return true; } bool flag = disabledAbilities.Contains(value); if (!flag && AbilitySelectionGuard.TryGetSelectionHolder(value, out string holderInfo)) { if (!_loggedDisableBlockedBySelection) { _loggedDisableBlockedBySelection = true; Plugin.Log.LogInfo((object)"Disable block fired because ability is currently selected by a player."); } Plugin.Log.LogInfo((object)$"Blocked disabling ability index {value} via right-click because it is currently selected ({holderInfo})."); return false; } if (flag) { disabledAbilities.Remove(value); } else { disabledAbilities.Add(value); } Plugin.Instance.SaveDisabledAbilities(disabledAbilities); bool flag2 = disabledAbilities.Contains(value); int num = -1; if ((Object)(object)value2 != (Object)null) { num = Traverse.Create((object)value2).Method("get_SelectedIcon", Array.Empty<object>()).GetValue<int>(); } if ((Object)(object)value2 != (Object)null) { val.SetColor(flag2 ? Color.red : ((Graphic)value2.borderImage).color); } Plugin.Log.LogInfo((object)string.Format("RightClickToggle: entryIndex={0}, abilityChoiceEquivalent={1}, selectedIcon={2}, nowDisabled={3}, disabledList=[{4}]", value, value + 1, num, flag2, string.Join(",", disabledAbilities.OrderBy((int i) => i)))); Plugin.Log.LogInfo((object)string.Format("{0} ability index {1} via right-click.", flag2 ? "Disabled" : "Enabled", value)); return false; } } [HarmonyPatch] internal static class CanaryPatch { private static bool _loggedPrintTextStart; private static int _callOnHoverClicksLogged; private static IEnumerable<MethodBase> TargetMethods() { List<MethodBase> list = new List<MethodBase>(); Type type = AccessTools.TypeByName("printText"); MethodInfo methodInfo = AccessTools.Method(type, "Start", (Type[])null, (Type[])null); if (methodInfo != null) { list.Add(methodInfo); } Type type2 = AccessTools.TypeByName("CallOnHover"); MethodInfo methodInfo2 = AccessTools.Method(type2, "OnPointerClick", new Type[1] { typeof(PointerEventData) }, (Type[])null); if (methodInfo2 != null) { list.Add(methodInfo2); } return list; } private static void Postfix(MethodBase __originalMethod, object[] __args) { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) string a = __originalMethod.DeclaringType?.Name + "." + __originalMethod.Name; if (string.Equals(a, "printText.Start", StringComparison.Ordinal)) { if (!_loggedPrintTextStart) { _loggedPrintTextStart = true; Plugin.Log.LogInfo((object)"Canary: printText.Start patch fired."); } } else if (string.Equals(a, "CallOnHover.OnPointerClick", StringComparison.Ordinal) && _callOnHoverClicksLogged < 8) { _callOnHoverClicksLogged++; object? obj = __args.FirstOrDefault(); PointerEventData val = (PointerEventData)((obj is PointerEventData) ? obj : null); object obj2; if (val == null) { obj2 = null; } else { InputButton button = val.button; obj2 = ((object)(InputButton)(ref button)).ToString(); } if (obj2 == null) { obj2 = "null"; } string arg = (string)obj2; Plugin.Log.LogInfo((object)$"Canary: CallOnHover.OnPointerClick fired. button={arg}, count={_callOnHoverClicksLogged}."); } } } [HarmonyPatch(typeof(CharacterSelectBox))] internal static class CharacterSelectDefaultAbilityPatch { private static bool _loggedJoinSanitize; private static bool _loggedLoadSanitize; [HarmonyPostfix] [HarmonyPatch("OnEnterSelect")] private static void OnEnterSelectPostfix(CharacterSelectBox __instance) { SanitizeAbilitySelections(__instance, "OnEnterSelect", ref _loggedJoinSanitize); } [HarmonyPostfix] [HarmonyPatch("SetToPlayerChoices")] private static void SetToPlayerChoicesPostfix(CharacterSelectBox __instance) { SanitizeAbilitySelections(__instance, "SetToPlayerChoices", ref _loggedLoadSanitize); } private static void SanitizeAbilitySelections(CharacterSelectBox box, string source, ref bool logged) { HashSet<int> disabledAbilities = Plugin.Instance.GetDisabledAbilities(); if (disabledAbilities.Count == 0) { return; } List<SelectableMonoBehaviour> value = Traverse.Create((object)box).Field<List<SelectableMonoBehaviour>>("selectables").Value; if (value == null) { return; } SelectAbility[] array = value.OfType<SelectAbility>().ToArray(); if (array.Length == 0) { return; } HashSet<int> hashSet = new HashSet<int>(from selector in array select Traverse.Create((object)selector).Field<int>("selectedIndex").Value into selectedIndex where IsEnabledSelection(selectedIndex, disabledAbilities) select selectedIndex); bool flag = false; SelectAbility[] array2 = array; foreach (SelectAbility val in array2) { int value2 = Traverse.Create((object)val).Field<int>("selectedIndex").Value; if (!IsBlacklistedSelection(value2, disabledAbilities)) { continue; } hashSet.Remove(value2); int valueOrDefault = (Traverse.Create((object)val).Field<NamedSpriteList>("abilityIcons").Value?.sprites?.Count).GetValueOrDefault(); if (valueOrDefault > 0) { int num = FindClosestEnabledSelection(value2, valueOrDefault, disabledAbilities, hashSet); if (num > 1 && num != value2) { ((SelectableMonoBehaviour)val).Select(num); hashSet.Add(num); flag = true; Plugin.Log.LogInfo((object)$"{source} replaced blacklisted default spriteIndex={value2}, abilityIndex={value2 - 1} with spriteIndex={num}, abilityIndex={num - 1}."); } } } if (flag) { Traverse.Create((object)box).Method("UpdatePlayerInit", Array.Empty<object>()).GetValue(); if (!logged) { logged = true; Plugin.Log.LogInfo((object)(source + " sanitized starting abilities against the blacklist.")); } } } private static int FindClosestEnabledSelection(int startIndex, int spriteCount, HashSet<int> disabledAbilities, HashSet<int> reservedSelections) { for (int i = 1; i < spriteCount; i++) { int num = startIndex - i; if (IsEnabledSelection(num, spriteCount, disabledAbilities, reservedSelections)) { return num; } int num2 = startIndex + i; if (IsEnabledSelection(num2, spriteCount, disabledAbilities, reservedSelections)) { return num2; } } for (int j = 1; j < spriteCount; j++) { int num3 = startIndex - j; if (IsEnabledSelection(num3, spriteCount, disabledAbilities)) { return num3; } int num4 = startIndex + j; if (IsEnabledSelection(num4, spriteCount, disabledAbilities)) { return num4; } } return startIndex; } private static bool IsBlacklistedSelection(int selectedIndex, HashSet<int> disabledAbilities) { int num = selectedIndex - 1; return num > 0 && disabledAbilities.Contains(num); } private static bool IsEnabledSelection(int selectedIndex, HashSet<int> disabledAbilities) { int num = selectedIndex - 1; return selectedIndex > 1 && num > 0 && !disabledAbilities.Contains(num); } private static bool IsEnabledSelection(int selectedIndex, int spriteCount, HashSet<int> disabledAbilities, HashSet<int> reservedSelections) { return selectedIndex > 1 && selectedIndex < spriteCount && IsEnabledSelection(selectedIndex, disabledAbilities) && !reservedSelections.Contains(selectedIndex); } private static bool IsEnabledSelection(int selectedIndex, int spriteCount, HashSet<int> disabledAbilities) { return selectedIndex > 1 && selectedIndex < spriteCount && IsEnabledSelection(selectedIndex, disabledAbilities); } } [HarmonyPatch(typeof(CharacterSelectBox))] internal static class CharacterSelectBoxDiagnosticsPatch { private static bool _loggedAwake; private static bool _loggedEnterAbilityGrid; private static bool _loggedClickEnterAbilityGrid; private static bool _loggedUpdate; private static bool _loggedCloseAbilityGridBlock; [HarmonyPostfix] [HarmonyPatch("Awake")] private static void AwakePostfix(CharacterSelectBox __instance) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!_loggedAwake) { _loggedAwake = true; AbilityGrid value = Traverse.Create((object)__instance).Field<AbilityGrid>("abilityGrid").Value; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)$"CharacterSelectBox.Awake fired in scene '{((Scene)(ref activeScene)).name}'. abilityGridNull={(Object)(object)value == (Object)null}"); } } [HarmonyPostfix] [HarmonyPatch("ClickEnterAbilityGrid")] private static void ClickEnterAbilityGridPostfix() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!_loggedClickEnterAbilityGrid) { _loggedClickEnterAbilityGrid = true; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)("CharacterSelectBox.ClickEnterAbilityGrid fired in scene '" + ((Scene)(ref activeScene)).name + "'.")); } } [HarmonyPostfix] [HarmonyPatch("EnterAbilityGrid")] private static void EnterAbilityGridPostfix(CharacterSelectBox __instance) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!_loggedEnterAbilityGrid) { _loggedEnterAbilityGrid = true; AbilityGrid value = Traverse.Create((object)__instance).Field<AbilityGrid>("abilityGrid").Value; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)$"CharacterSelectBox.EnterAbilityGrid fired in scene '{((Scene)(ref activeScene)).name}'. abilityGridNull={(Object)(object)value == (Object)null}"); } } [HarmonyPostfix] [HarmonyPatch("Update")] private static void UpdatePostfix(CharacterSelectBox __instance) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) if (!_loggedUpdate) { _loggedUpdate = true; AbilityGrid value = Traverse.Create((object)__instance).Field<AbilityGrid>("abilityGrid").Value; int value2 = Traverse.Create((object)__instance).Field<int>("ab1Index").Value; int value3 = Traverse.Create((object)__instance).Field<int>("ab2Index").Value; int value4 = Traverse.Create((object)__instance).Field<int>("ab3Index").Value; ManualLogSource log = Plugin.Log; object[] array = new object[5]; Scene activeScene = SceneManager.GetActiveScene(); array[0] = ((Scene)(ref activeScene)).name; array[1] = (Object)(object)value == (Object)null; array[2] = value2; array[3] = value3; array[4] = value4; log.LogInfo((object)string.Format("CharacterSelectBox.Update fired in scene '{0}'. abilityGridNull={1}, abilities=[{2},{3},{4}]", array)); } } [HarmonyPrefix] [HarmonyPatch("CloseAbilityGrid")] private static bool CloseAbilityGridPrefix(CharacterSelectBox __instance, int __0) { int num = __0 - 1; if (num <= 0) { return true; } HashSet<int> disabledAbilities = Plugin.Instance.GetDisabledAbilities(); AbilityGrid value = Traverse.Create((object)__instance).Field<AbilityGrid>("abilityGrid").Value; int num2 = (((Object)(object)value == (Object)null) ? (-1) : Traverse.Create((object)value).Method("get_SelectedIcon", Array.Empty<object>()).GetValue<int>()); bool flag = disabledAbilities.Contains(num); bool flag2 = num2 == num; Plugin.Log.LogInfo((object)$"CloseAbilityGrid: abilityChoice={__0}, abilityEntryIndex={num}, selectedIcon={num2}, selectedMatches={flag2}, shouldBlock={flag}"); if (!flag) { return true; } if (!_loggedCloseAbilityGridBlock) { _loggedCloseAbilityGridBlock = true; Plugin.Log.LogInfo((object)"CharacterSelectBox.CloseAbilityGrid block patch fired for disabled abilities."); } Plugin.Log.LogInfo((object)$"Blocked closing ability grid for disabled index {num}."); return false; } } [HarmonyPatch(typeof(CharacterSelectHandler))] internal static class CharacterSelectHandlerDiagnosticsPatch { private static bool _loggedAwake; private static bool _loggedUpdate; [HarmonyPostfix] [HarmonyPatch("Awake")] private static void AwakePostfix() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!_loggedAwake) { _loggedAwake = true; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)("CharacterSelectHandler.Awake fired in scene '" + ((Scene)(ref activeScene)).name + "'.")); } } [HarmonyPostfix] [HarmonyPatch("Update")] private static void UpdatePostfix(CharacterSelectHandler __instance) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!_loggedUpdate) { _loggedUpdate = true; CharacterSelectBox[] value = Traverse.Create((object)__instance).Field<CharacterSelectBox[]>("characterSelectBoxes").Value; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)$"CharacterSelectHandler.Update fired in scene '{((Scene)(ref activeScene)).name}'. boxes={((value == null) ? (-1) : value.Length)}"); } } } [HarmonyPatch(typeof(CharacterSelectHandler_online))] internal static class CharacterSelectHandlerOnlineDiagnosticsPatch { private static bool _loggedAwake; private static bool _loggedUpdate; private static bool _loggedOnLobbyInit; [HarmonyPostfix] [HarmonyPatch("Awake")] private static void AwakePostfix() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!_loggedAwake) { _loggedAwake = true; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)("CharacterSelectHandler_online.Awake fired in scene '" + ((Scene)(ref activeScene)).name + "'.")); } } [HarmonyPostfix] [HarmonyPatch("Update")] private static void UpdatePostfix(CharacterSelectHandler_online __instance) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (!_loggedUpdate) { _loggedUpdate = true; CharacterSelectBox value = Traverse.Create((object)__instance).Field<CharacterSelectBox>("characterSelectBox").Value; CSBox_online[] value2 = Traverse.Create((object)__instance).Field<CSBox_online[]>("networkPlayerBoxes").Value; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)$"CharacterSelectHandler_online.Update fired in scene '{((Scene)(ref activeScene)).name}'. localBoxNull={(Object)(object)value == (Object)null}, networkBoxes={((value2 == null) ? (-1) : value2.Length)}"); } } [HarmonyPostfix] [HarmonyPatch("OnLobbyInit")] private static void OnLobbyInitPostfix() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!_loggedOnLobbyInit) { _loggedOnLobbyInit = true; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)("CharacterSelectHandler_online.OnLobbyInit fired in scene '" + ((Scene)(ref activeScene)).name + "'.")); } } } [HarmonyPatch(typeof(CharSelectClickToJoin))] internal static class CharSelectClickToJoinDiagnosticsPatch { private static bool _loggedOnEnable; private static bool _loggedClick; [HarmonyPostfix] [HarmonyPatch("OnEnable")] private static void OnEnablePostfix() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!_loggedOnEnable) { _loggedOnEnable = true; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)("CharSelectClickToJoin.OnEnable fired in scene '" + ((Scene)(ref activeScene)).name + "'.")); } } [HarmonyPostfix] [HarmonyPatch("OnPointerClick")] private static void OnPointerClickPostfix() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!_loggedClick) { _loggedClick = true; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)("CharSelectClickToJoin.OnPointerClick fired in scene '" + ((Scene)(ref activeScene)).name + "'.")); } } } [HarmonyPatch(typeof(GameSessionHandler))] internal static class DiagnosticsGameSessionHandlerPatch { private static bool _loggedInit; private static bool _loggedLoadAbilitySelectScene; [HarmonyPostfix] [HarmonyPatch("Init")] private static void InitPostfix() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!_loggedInit) { _loggedInit = true; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)("GameSessionHandler.Init fired in scene '" + ((Scene)(ref activeScene)).name + "'.")); } } [HarmonyPostfix] [HarmonyPatch("LoadAbilitySelectScene")] private static void LoadAbilitySelectScenePostfix() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!_loggedLoadAbilitySelectScene) { _loggedLoadAbilitySelectScene = true; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)("GameSessionHandler.LoadAbilitySelectScene fired in scene '" + ((Scene)(ref activeScene)).name + "'.")); } } } [HarmonyPatch(typeof(SelectAbility))] internal static class DiagnosticsSelectAbilityPatch { private static bool _loggedAwake; private static bool _loggedSelect; [HarmonyPostfix] [HarmonyPatch("Awake")] private static void AwakePostfix() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!_loggedAwake) { _loggedAwake = true; ManualLogSource log = Plugin.Log; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)("SelectAbility.Awake fired in scene '" + ((Scene)(ref activeScene)).name + "'.")); } } [HarmonyPostfix] [HarmonyPatch("Select")] [HarmonyPatch(new Type[] { typeof(int) })] private static void SelectPostfix(int index) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (!_loggedSelect) { _loggedSelect = true; ManualLogSource log = Plugin.Log; object arg = index; Scene activeScene = SceneManager.GetActiveScene(); log.LogInfo((object)$"SelectAbility.Select fired with index {arg} in scene '{((Scene)(ref activeScene)).name}'."); } } } [HarmonyPatch(typeof(MidGameAbilitySelect))] internal static class MidGameAbilitySelectPatch { private static bool _loggedPatch; private static bool _loggedSkip; private static bool _loggedNoEnabledFallback; [HarmonyPostfix] [HarmonyPatch("Left")] private static void LeftPostfix(MidGameAbilitySelect __instance) { SkipBlacklistedSelection(__instance, moveForward: true); } [HarmonyPostfix] [HarmonyPatch("Right")] private static void RightPostfix(MidGameAbilitySelect __instance) { SkipBlacklistedSelection(__instance, moveForward: false); } private static void SkipBlacklistedSelection(MidGameAbilitySelect selector, bool moveForward) { HashSet<int> disabledAbilities = Plugin.Instance.GetDisabledAbilities(); if (disabledAbilities.Count == 0) { return; } if (!_loggedPatch) { _loggedPatch = true; Plugin.Log.LogInfo((object)"MidGameAbilitySelect.Left/Right scroller patch fired."); } int[] value = Traverse.Create((object)selector).Field<int[]>("XPos").Value; if (value == null || value.Length == 0) { return; } int value2 = Traverse.Create((object)selector).Field<int>("YPos").Value; if (value2 < 0 || value2 >= value.Length) { return; } int num = value[value2]; if (!IsBlacklistedSelection(num, disabledAbilities)) { return; } int valueOrDefault = (Traverse.Create((object)selector).Field<NamedSpriteList>("localAbilityIcons").Value?.sprites?.Count).GetValueOrDefault(); if (valueOrDefault <= 0) { return; } int num2 = FindNextEnabledSelection(num, moveForward, valueOrDefault, disabledAbilities); if (num2 == num || num2 <= 0) { if (!_loggedNoEnabledFallback) { _loggedNoEnabledFallback = true; Plugin.Log.LogWarning((object)"MidGameAbilitySelect could not find an enabled replacement for a disabled selection."); } return; } value[value2] = num2; Traverse.Create((object)selector).Method("UpdatePlayerAbilityChoices", Array.Empty<object>()).GetValue(); if (!_loggedSkip) { _loggedSkip = true; Plugin.Log.LogInfo((object)"MidGameAbilitySelect scroller skip fired for disabled abilities."); } Plugin.Log.LogInfo((object)string.Format("MidGameAbilitySelect {0} skipped disabled spriteIndex={1}, abilityIndex={2}, replacementSpriteIndex={3}, replacementAbilityIndex={4}.", moveForward ? "Left" : "Right", num, num - 1, num2, num2 - 1)); } private static int FindNextEnabledSelection(int startIndex, bool moveForward, int spriteCount, HashSet<int> disabledAbilities) { int num = (moveForward ? 1 : (-1)); int num2 = startIndex; for (int i = 0; i < spriteCount; i++) { num2 = (num2 + num + spriteCount) % spriteCount; if (num2 != 0 && !IsBlacklistedSelection(num2, disabledAbilities)) { return num2; } } return startIndex; } private static bool IsBlacklistedSelection(int selectedIndex, HashSet<int> disabledAbilities) { int num = selectedIndex - 1; return num > 0 && disabledAbilities.Contains(num); } } internal static class RandomAbilityPatch { private static bool _loggedRandomPatch; internal static void ApplyBlacklistToRandomResult(ref NamedSprite result, NamedSpriteList abilityIcons, NamedSpriteList abilityIconsDemo) { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) if (!_loggedRandomPatch) { Plugin.Log.LogInfo((object)"RandomAbility.GetRandomAbilityPrefab patch fired."); _loggedRandomPatch = true; } NamedSpriteList val = (ShouldUseDlcPool() ? abilityIcons : abilityIconsDemo); if (val?.sprites == null) { return; } int count = val.sprites.Count; int num = count - 2; if (num <= 0) { return; } HashSet<int> disabledAbilities = Plugin.Instance.GetDisabledAbilities(); List<int> list = new List<int>(num); for (int i = 2; i < count; i++) { if (!disabledAbilities.Contains(i - 1)) { list.Add(i); } } if (list.Count == 0) { Plugin.Log.LogWarning((object)"All abilities are blacklisted. Leaving the game's original random selection unchanged."); return; } int index = Updater.RandomInt(0, list.Count); int index2 = list[index]; result = val.sprites[index2]; } private static bool ShouldUseDlcPool() { if ((Object)(object)SteamManager.instance == (Object)null) { return false; } return SteamManager.instance.dlc.HasDLC(); } } [HarmonyPatch(typeof(RandomAbility), "GetRandomAbilityPrefab")] [HarmonyPatch(new Type[] { typeof(NamedSpriteList), typeof(NamedSpriteList) })] internal static class RandomAbilityTwoArgPatch { [HarmonyPostfix] private static void Postfix(ref NamedSprite __result, NamedSpriteList abilityIcons, NamedSpriteList abilityIcons_demo) { RandomAbilityPatch.ApplyBlacklistToRandomResult(ref __result, abilityIcons, abilityIcons_demo); } } [HarmonyPatch(typeof(RandomAbility), "GetRandomAbilityPrefab")] [HarmonyPatch(new Type[] { typeof(NamedSpriteList), typeof(NamedSpriteList), typeof(Sprite) })] internal static class RandomAbilityThreeArgPatch { [HarmonyPostfix] private static void Postfix(ref NamedSprite __result, NamedSpriteList abilityIcons, NamedSpriteList abilityIcons_demo) { RandomAbilityPatch.ApplyBlacklistToRandomResult(ref __result, abilityIcons, abilityIcons_demo); } } [HarmonyPatch(typeof(SelectAbility))] internal static class SelectAbilityScrollerPatch { private static bool _loggedScrollerSkip; [HarmonyPostfix] [HarmonyPatch("Move")] [HarmonyPatch(new Type[] { typeof(bool) })] private static void MovePostfix(SelectAbility __instance, bool moveRight) { HashSet<int> disabledAbilities = Plugin.Instance.GetDisabledAbilities(); if (disabledAbilities.Count == 0) { return; } int value = Traverse.Create((object)__instance).Field<int>("selectedIndex").Value; if (!IsBlacklistedSelection(value, disabledAbilities)) { return; } int valueOrDefault = (Traverse.Create((object)__instance).Field<NamedSpriteList>("abilityIcons").Value?.sprites?.Count).GetValueOrDefault(); if (valueOrDefault <= 0) { return; } int num = FindNextEnabledSelection(value, moveRight, valueOrDefault, disabledAbilities); if (num > 0 && num != value) { ((SelectableMonoBehaviour)__instance).Select(num); if (!_loggedScrollerSkip) { _loggedScrollerSkip = true; Plugin.Log.LogInfo((object)"SelectAbility.Move scroller skip fired for disabled abilities."); } Plugin.Log.LogInfo((object)$"SelectAbility.Move skipped disabled selection spriteIndex={value}, abilityIndex={value - 1}, replacementSpriteIndex={num}, replacementAbilityIndex={num - 1}, moveRight={moveRight}."); } } private static int FindNextEnabledSelection(int startIndex, bool moveRight, int spriteCount, HashSet<int> disabledAbilities) { int num = (moveRight ? 1 : (-1)); int num2 = startIndex; for (int i = 0; i < spriteCount; i++) { num2 = (num2 + num + spriteCount) % spriteCount; if (num2 != 0 && !IsBlacklistedSelection(num2, disabledAbilities)) { return num2; } } return startIndex; } private static bool IsBlacklistedSelection(int selectedIndex, HashSet<int> disabledAbilities) { int num = selectedIndex - 1; return num > 0 && disabledAbilities.Contains(num); } } [HarmonyPatch(typeof(SteamManager))] internal static class SteamManagerDiagnosticsPatch { private static bool _loggedCreated; private static bool _loggedEntered; private static int _lastSummaryHash; [HarmonyPostfix] [HarmonyPatch("OnLobbyCreatedCallback")] private static void OnLobbyCreatedCallbackPostfix() { if (!_loggedCreated) { _loggedCreated = true; LogRuntimeSnapshot("OnLobbyCreatedCallback"); } } [HarmonyPostfix] [HarmonyPatch("OnLobbyEnteredCallback")] private static void OnLobbyEnteredCallbackPostfix() { if (!_loggedEntered) { _loggedEntered = true; LogRuntimeSnapshot("OnLobbyEnteredCallback"); } } [HarmonyPostfix] [HarmonyPatch("Update")] private static void UpdatePostfix() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; string text = BuildCountSummary(); string text2 = name + "|" + text; int hashCode = text2.GetHashCode(); if (hashCode != _lastSummaryHash) { _lastSummaryHash = hashCode; Plugin.Log.LogInfo((object)("SteamManager.Update summary: scene='" + name + "', " + text)); } } private static void LogRuntimeSnapshot(string source) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); string[] value = (from go in ((Scene)(ref activeScene)).GetRootGameObjects() select ((Object)go).name into name orderby name select name).ToArray(); Plugin.Log.LogInfo((object)(source + ": scene='" + ((Scene)(ref activeScene)).name + "', roots=[" + string.Join(", ", value) + "], " + BuildCountSummary())); } private static string BuildCountSummary() { int num = Object.FindObjectsOfType<CharacterSelectBox>().Length; int num2 = Object.FindObjectsOfType<CharacterSelectHandler>().Length; int num3 = Object.FindObjectsOfType<CharacterSelectHandler_online>().Length; int num4 = Object.FindObjectsOfType<CSBox_online>().Length; int num5 = Object.FindObjectsOfType<CharSelectClickToJoin>().Length; int num6 = Object.FindObjectsOfType<SelectAbility>().Length; int num7 = Object.FindObjectsOfType<AbilityGrid>().Length; int num8 = Object.FindObjectsOfType<AbilityGridEntry>().Length; int num9 = Object.FindObjectsOfType<MenuAbilitySelector>().Length; int num10 = Object.FindObjectsOfType<AbilitySelectController>().Length; return $"counts: CharacterSelectBox={num}, CharacterSelectHandler={num2}, CharacterSelectHandler_online={num3}, CSBox_online={num4}, CharSelectClickToJoin={num5}, SelectAbility={num6}, AbilityGrid={num7}, AbilityGridEntry={num8}, MenuAbilitySelector={num9}, AbilitySelectController={num10}"; } } }