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 AutoAttack v0.1.0
plugins\AutoAttack.dll
Decompiled 3 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("AutoAttack")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("AutoAttack")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")] [assembly: AssemblyFileVersion("0.1.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] namespace AutoAttack; [BepInPlugin("orax.AutoAttack", "AutoAttack", "0.1.0")] public class AutoAttack : BaseUnityPlugin { public const string PluginGuid = "orax.AutoAttack"; public const string PluginName = "AutoAttack"; public const string PluginVersion = "0.1.0"; public static ConfigEntry<bool> Enabled; public static ConfigEntry<KeyboardShortcut> ToggleKey; public static ConfigEntry<string> GamepadToggleButtons; public static ConfigEntry<bool> CancelOnWeaponChange; public static ConfigEntry<bool> ShowOnScreenMessage; public static ConfigEntry<MessageType> ScreenMessagePosition; public static ConfigEntry<string> MessageOn; public static ConfigEntry<string> MessageOff; public static ConfigEntry<string> MessageCancelled; public static ConfigEntry<string> MessageWeaponChanged; internal static AutoAttack Instance; private static ManualLogSource _log; private Harmony _harmony; private static string[] _gamepadChord = new string[0]; private static string _gamepadChordRaw; private static bool _gamepadChordHeldLastFrame; private static bool _gamepadChordValidated; internal static void LogStatic(string message) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)message); } } private void Awake() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Expected O, but got Unknown Instance = this; _log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch for the mod. When disabled, continuous attack is forced off and the game behaves exactly as vanilla (no toggle key, no pause/cancel buttons, no jump or weapon-swap adjustments)."); ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keyboard", "Toggle auto-attack key", new KeyboardShortcut((KeyCode)324, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Keyboard key combination that starts/stops continuous attacking (equivalent to holding down the attack button). Written as Unity KeyCode names - first the key to press, then the modifiers held with it, separated by + or , (default Mouse1 + LeftAlt). Names are case-sensitive (Mouse1, not mouse1), and the combination only fires while no other keyboard key is held (mouse buttons don't count). Mouse buttons: Mouse0 (left) to Mouse6. Full list of key names: https://docs.unity3d.com/6000.0/Documentation/ScriptReference/KeyCode.html"); CancelOnWeaponChange = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Cancel on weapon change", true, "Cancel continuous attack if the equipped weapon changes while it is active (swapping to another weapon, unequipping, or the weapon being destroyed)."); ShowOnScreenMessage = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "Show on screen message", true, "Show a short on-screen message when continuous attack turns on, off, or gets cancelled."); ScreenMessagePosition = ((BaseUnityPlugin)this).Config.Bind<MessageType>("HUD", "Screen message position", (MessageType)1, "Position of the screen message."); MessageOn = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Turned on", "Continuous attack: ON", "Text shown when the toggle turns on. Leave empty to hide this message."); MessageOff = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Turned off", "Continuous attack: OFF", "Text shown when the toggle turns off. Leave empty to hide this message."); MessageCancelled = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Cancelled", "Continuous attack: cancelled", "Text shown when a cancel button stops continuous attack. Leave empty to hide this message."); MessageWeaponChanged = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Cancelled (weapon changed)", "Continuous attack: cancelled (weapon changed)", "Text shown when continuous attack stops because the equipped weapon changed (only when \"Cancel on weapon change\" is enabled). Leave empty to hide this message."); _harmony = new Harmony("orax.AutoAttack"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"AutoAttack 0.1.0 loaded."); } private void Update() { //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) if (DynamicButtonConfig.CancelEntries.Count == 0) { DynamicButtonConfig.Generate(((BaseUnityPlugin)this).Config); } bool flag = GamepadToggleDown(); if (!Enabled.Value) { AutoAttackState.Reset(); return; } if ((Object)(object)Player.m_localPlayer == (Object)null) { AutoAttackState.Reset(); return; } AutoAttackState.Paused = DynamicButtonConfig.AnyHeld(DynamicButtonConfig.PauseEntries); bool flag2 = false; if (!Minimap.InTextInput()) { KeyboardShortcut value = ToggleKey.Value; if (((KeyboardShortcut)(ref value)).IsDown() || flag) { bool flag3 = AutoAttackState.Toggle(); ShowMessage(flag3 ? MessageOn.Value : MessageOff.Value); flag2 = true; } } if (!flag2 && AutoAttackState.Active && DynamicButtonConfig.AnyDown(DynamicButtonConfig.CancelEntries)) { AutoAttackState.Reset(); ShowMessage(MessageCancelled.Value); } if (!flag2 && AutoAttackState.Active && CancelOnWeaponChange.Value && ((Humanoid)Player.m_localPlayer).GetCurrentWeapon() != AutoAttackState.ActiveWeapon) { AutoAttackState.Reset(); ShowMessage(MessageWeaponChanged.Value); } } internal void ShowMessage(string text) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(text) && ShowOnScreenMessage.Value && (Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage(ScreenMessagePosition.Value, text, 0, (Sprite)null, false, true); } } private bool GamepadToggleDown() { if (GamepadToggleButtons == null) { return false; } string value = GamepadToggleButtons.Value; if (!string.Equals(value, _gamepadChordRaw, StringComparison.Ordinal)) { _gamepadChordRaw = value; _gamepadChord = (from part in (value ?? string.Empty).Split(new char[2] { ',', '+' }, StringSplitOptions.RemoveEmptyEntries) select part.Trim() into part where part.Length > 0 select part).ToArray(); _gamepadChordHeldLastFrame = false; _gamepadChordValidated = false; } bool flag = _gamepadChord.Length != 0; int num = 0; while (flag && num < _gamepadChord.Length) { flag = ZInput.GetButton(_gamepadChord[num]); num++; } bool result = flag && !_gamepadChordHeldLastFrame; _gamepadChordHeldLastFrame = flag; if (!_gamepadChordValidated && DynamicButtonConfig.CancelEntries.Count > 0) { _gamepadChordValidated = true; string[] gamepadChord = _gamepadChord; foreach (string text in gamepadChord) { if (!DynamicButtonConfig.IsRegisteredButton(text)) { LogStatic("Gamepad toggle: ZInput has no button named \"" + text + "\" - it can never complete the combo. Check [Gamepad] Toggle auto-attack buttons."); } } } return result; } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal static class DynamicButtonConfig { public static readonly Dictionary<string, ConfigEntry<bool>> CancelEntries = new Dictionary<string, ConfigEntry<bool>>(); public static readonly Dictionary<string, ConfigEntry<bool>> PauseEntries = new Dictionary<string, ConfigEntry<bool>>(); private static readonly FieldRef<ZInput, Dictionary<string, ButtonDef>> ButtonsRef = AccessTools.FieldRefAccess<ZInput, Dictionary<string, ButtonDef>>("m_buttons"); public static bool IsRegisteredButton(string name) { ZInput instance = ZInput.instance; if (instance != null) { return ButtonsRef.Invoke(instance).ContainsKey(name); } return false; } public static void Generate(ConfigFile config) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Invalid comparison between Unknown and I4 if (CancelEntries.Count > 0) { return; } ZInput instance = ZInput.instance; if (instance == null) { return; } List<ButtonDef> list = ButtonsRef.Invoke(instance).Values.Where((ButtonDef b) => b.Rebindable || (int)b.Source == 180).OrderBy<ButtonDef, string>((ButtonDef b) => b.Name, StringComparer.OrdinalIgnoreCase).ToList(); if (AutoAttack.GamepadToggleButtons == null) { AutoAttack.GamepadToggleButtons = config.Bind<string>("Gamepad", "Toggle auto-attack buttons", "JoyLBumper, JoyRBumper", BuildGamepadChordDescription(list)); } foreach (ButtonDef item in list) { string name = item.Name; bool flag = (int)item.Source == 180; string text = DescribeButton(instance, name); string text2 = (flag ? "Gamepad cancel buttons" : "Keyboard cancel buttons"); string text3 = (flag ? "Gamepad pause buttons" : "Keyboard pause buttons"); bool flag3; if (flag) { bool flag2; switch (name) { case "JoyAttack": case "JoySecondaryAttack": case "JoyBlock": case "JoyUse": case "JoyCrouch": case "JoySit": flag2 = true; break; default: flag2 = false; break; } flag3 = flag2; } else { bool flag2; switch (name) { case "Attack": case "Crouch": case "Sit": case "Use": case "SecondaryAttack": case "Block": case "AltDodge": case "AutoRun": flag2 = true; break; default: flag2 = false; break; } flag3 = flag2; } bool flag4 = flag3; CancelEntries[name] = config.Bind<bool>(text2, name, flag4, flag ? ("Pressing " + text + " fully cancels continuous attack. Raw name \"" + name + "\", also usable in [Gamepad] Toggle auto-attack buttons.") : ("Pressing " + text + " fully cancels continuous attack.")); if (name == "Attack" || name == "JoyAttack") { continue; } if (flag) { bool flag2; switch (name) { case "JoyLStickDown": case "JoyLStickLeft": case "JoySecondaryAttack": case "JoyJump": case "JoyBlock": case "JoyLStickUp": case "JoyLStickRight": flag2 = true; break; default: flag2 = false; break; } flag3 = flag2; } else { bool flag2; switch (name) { case "Jump": case "Left": case "Block": case "Right": case "SecondaryAttack": case "Forward": case "Backward": flag2 = true; break; default: flag2 = false; break; } flag3 = flag2; } bool flag5 = flag3; PauseEntries[name] = config.Bind<bool>(text3, name, flag5, flag ? ("Holding " + text + " temporarily pauses continuous attack; it resumes automatically on release. Raw name \"" + name + "\", also usable in [Gamepad] Toggle auto-attack buttons.") : ("Holding " + text + " temporarily pauses continuous attack; it resumes automatically on release.")); } AutoAttack.LogStatic($"Generated {CancelEntries.Count} cancel / {PauseEntries.Count} pause button entries from ZInput (keyboard + gamepad)."); } private static string BuildGamepadChordDescription(List<ButtonDef> buttons) { string[] array = (from b in buttons where (int)b.Source == 180 select b.Name).ToArray(); string text = "Gamepad buttons that must all be held together to start/stop continuous attacking, as comma or plus separated ZInput button names (default JoyLBumper, JoyRBumper = L1 + R1). The combo fires on the press that completes it, like the keyboard combination. Leave empty to disable the gamepad toggle.\n"; if (array.Length == 0) { return text; } return text + "\nAll button names available in this game, generated from ZInput at startup (controller layout: " + ((object)Unsafe.As<InputLayout, InputLayout>(ref ZInput.InputLayout)/*cast due to .constrained prefix*/).ToString() + "; restart the game after changing the layout). Each name is also listed with its in-game label and current binding under [Gamepad cancel buttons]:\n" + WrapNameList(array); } private static string WrapNameList(string[] names) { List<string> list = new List<string>(); string text = null; int num = 0; foreach (string text2 in names) { int num2 = text2.Length + ((text != null) ? 2 : 0); if (text != null && num + num2 > 100) { list.Add(text); text = null; num = 0; num2 = text2.Length; } text = ((text == null) ? text2 : (text + ", " + text2)); num += num2; } if (text != null) { list.Add(text); } return string.Join("\n", list); } private static string DescribeButton(ZInput instance, string name) { string text = null; try { if (Localization.instance != null) { text = Localization.instance.Localize("$settings_" + name.ToLower()); } } catch (Exception ex) { AutoAttack.LogStatic("Could not localize button \"" + name + "\": " + ex.Message); } string text2 = null; try { text2 = instance.GetBoundKeyString(name, true); if (!string.IsNullOrEmpty(text2) && Localization.instance != null) { text2 = Localization.instance.Localize(text2); } } catch (Exception ex2) { AutoAttack.LogStatic("Could not read bound key for \"" + name + "\": " + ex2.Message); } bool flag = !string.IsNullOrEmpty(text) && text != name; bool flag2 = !string.IsNullOrEmpty(text2); if (flag && flag2) { return "\"" + name + "\" (" + text + ", currently bound to: " + text2 + ")"; } if (flag) { return "\"" + name + "\" (" + text + ")"; } if (flag2) { return "\"" + name + "\" (currently bound to: " + text2 + ")"; } return "\"" + name + "\""; } public static bool AnyDown(Dictionary<string, ConfigEntry<bool>> entries) { foreach (KeyValuePair<string, ConfigEntry<bool>> entry in entries) { if (entry.Value.Value && ZInput.GetButtonDown(entry.Key)) { return true; } } return false; } public static bool AnyHeld(Dictionary<string, ConfigEntry<bool>> entries) { foreach (KeyValuePair<string, ConfigEntry<bool>> entry in entries) { if (entry.Value.Value && ZInput.GetButton(entry.Key)) { return true; } } return false; } } [HarmonyPatch(typeof(ZInput), "Initialize")] internal static class ZInput_Initialize_GenerateButtonConfig { private static void Postfix() { if ((Object)(object)AutoAttack.Instance != (Object)null) { DynamicButtonConfig.Generate(((BaseUnityPlugin)AutoAttack.Instance).Config); } } } internal static class AutoAttackState { public static bool Active { get; private set; } public static bool Paused { get; set; } public static ItemData ActiveWeapon { get; private set; } public static bool Toggle() { Active = !Active; Paused = false; object activeWeapon; if (!Active) { activeWeapon = null; } else { Player localPlayer = Player.m_localPlayer; activeWeapon = ((localPlayer != null) ? ((Humanoid)localPlayer).GetCurrentWeapon() : null); } ActiveWeapon = (ItemData)activeWeapon; return Active; } public static void Reset() { Active = false; Paused = false; ActiveWeapon = null; } } [HarmonyPatch(typeof(ZInput), "GetButton", new Type[] { typeof(string) })] internal static class ZInput_GetButton_ForceAttackHold { private static readonly FieldRef<Humanoid, float> AttackDrawTimeRef = AccessTools.FieldRefAccess<Humanoid, float>("m_attackDrawTime"); private static void Postfix(string name, ref bool __result) { if (!__result && AutoAttackState.Active && !AutoAttackState.Paused && !(name != "Attack") && !ReloadPending() && !BowDrawStuck() && !BowFullyDrawn()) { __result = true; } } private static bool BowDrawStuck() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } ItemData currentWeapon = ((Humanoid)localPlayer).GetCurrentWeapon(); if (currentWeapon == null || !currentWeapon.m_shared.m_attack.m_bowDraw) { return false; } return AttackDrawTimeRef.Invoke((Humanoid)(object)localPlayer) < 0f; } private static bool ReloadPending() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } ItemData currentWeapon = ((Humanoid)localPlayer).GetCurrentWeapon(); if (currentWeapon != null && currentWeapon.m_shared.m_attack.m_requiresReload) { return !((Humanoid)localPlayer).IsWeaponLoaded(); } return false; } private static bool BowFullyDrawn() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } ItemData currentWeapon = ((Humanoid)localPlayer).GetCurrentWeapon(); if (currentWeapon == null || !currentWeapon.m_shared.m_attack.m_bowDraw) { return false; } if (((Character)localPlayer).InAttack()) { return false; } return ((Humanoid)localPlayer).GetAttackDrawPercentage() >= 1f; } } [HarmonyPatch(typeof(Character), "Jump", new Type[] { typeof(bool) })] internal static class Character_Jump_AllowWhileAutoAttack { private static void Prefix(Character __instance, ref bool force) { if (!force && AutoAttackState.Active && (Object)(object)__instance == (Object)(object)Player.m_localPlayer && (ZInput.GetButton("Jump") || ZInput.GetButton("JoyJump"))) { force = true; } } } internal static class EquipDuringAutoAttack { private static readonly MethodInfo BlocksEquipMethod = AccessTools.Method(typeof(EquipDuringAutoAttack), "BlocksEquip", (Type[])null, (Type[])null); private static readonly MethodInfo BlocksQueuedActionMethod = AccessTools.Method(typeof(EquipDuringAutoAttack), "BlocksQueuedAction", (Type[])null, (Type[])null); private static readonly FieldRef<Player, List<MinorActionData>> ActionQueueRef = AccessTools.FieldRefAccess<Player, List<MinorActionData>>("m_actionQueue"); private static bool BypassEnabled(Humanoid self) { if (AutoAttackState.Active) { return (Object)(object)self == (Object)(object)Player.m_localPlayer; } return false; } public static bool BlocksEquip(Humanoid self) { if (!BypassEnabled(self)) { return ((Character)self).InAttack(); } return false; } public static bool BlocksQueuedAction(Humanoid self) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0035: Invalid comparison between Unknown and I4 if (BypassEnabled(self)) { List<MinorActionData> list = ActionQueueRef.Invoke((Player)self); if (list.Count > 0) { ActionType type = list[0].m_type; if ((int)type == 0 || (int)type == 1) { return false; } } } return ((Character)self).InAttack(); } public static IEnumerable<CodeInstruction> TranspileBlocksEquip(IEnumerable<CodeInstruction> instructions) { return ReplaceInAttackCall(instructions, BlocksEquipMethod); } public static IEnumerable<CodeInstruction> TranspileBlocksQueuedAction(IEnumerable<CodeInstruction> instructions) { return ReplaceInAttackCall(instructions, BlocksQueuedActionMethod); } private static IEnumerable<CodeInstruction> ReplaceInAttackCall(IEnumerable<CodeInstruction> instructions, MethodInfo replacement) { foreach (CodeInstruction instruction in instructions) { if ((instruction.opcode == OpCodes.Callvirt || instruction.opcode == OpCodes.Call) && instruction.operand is MethodInfo { Name: "InAttack" }) { instruction.opcode = OpCodes.Call; instruction.operand = replacement; } yield return instruction; } } } [HarmonyPatch(typeof(Player), "ToggleEquipped")] internal static class Player_ToggleEquipped_AllowSwapDuringAutoAttack { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return EquipDuringAutoAttack.TranspileBlocksEquip(instructions); } } [HarmonyPatch(typeof(Humanoid), "EquipItem")] internal static class Humanoid_EquipItem_AllowSwapDuringAutoAttack { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return EquipDuringAutoAttack.TranspileBlocksEquip(instructions); } } [HarmonyPatch(typeof(Player), "UpdateActionQueue")] internal static class Player_UpdateActionQueue_AllowQueuedSwapDuringAutoAttack { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return EquipDuringAutoAttack.TranspileBlocksQueuedAction(instructions); } } [HarmonyPatch(typeof(Player), "Update")] internal static class Player_Update_AllowHideDuringAutoAttack { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return EquipDuringAutoAttack.TranspileBlocksEquip(instructions); } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class Player_OnDeath_ResetAutoAttack { private static void Postfix(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { AutoAttackState.Reset(); } } }