using System; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; 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("SpamKeyMod")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("SpamKeyMod")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("f72eb7b3-46b0-4e28-8909-055df9d8afca")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace SpamKeyMod; public enum SpamModLanguage { Russian, English } [BepInPlugin("Shinsu.spamkeymod", "Spam Key Mod", "2.0.0")] public class SpamKeyModPlugin : BaseUnityPlugin { private enum FishingPhase { WaitingFullStamina, Casting, WaitingCastConfirm, Reeling } private class ActionPulse { private float timer; public void Tick(float dt) { timer += dt; } public void Reset() { timer = 0f; } public bool ConsumeTick(float interval) { if (timer >= interval) { timer = 0f; return true; } return false; } } public const string PluginGUID = "Shinsu.spamkeymod"; public const string PluginName = "Spam Key Mod"; public const string PluginVersion = "2.0.0"; public static ManualLogSource Log; public static ConfigEntry Language; public static ConfigEntry TriggerKey; public static ConfigEntry Interval; public static ConfigEntry ShowIndicator; public static ConfigEntry DoAttack; public static ConfigEntry DoBlock; public static ConfigEntry DoJump; public static ConfigEntry DoUse; public static ConfigEntry DoRun; public static ConfigEntry DoSneak; public static ConfigEntry DoBow; public static ConfigEntry DoCultivator; public static ConfigEntry DoFishing; public static ConfigEntry DoSwim; public static ConfigEntry DoEitr; private static (ConfigEntry entry, string nameRu, string nameEn)[] modeToggles; public static ConfigEntry BlockHoldOverride; public static ConfigEntry DurabilityStopThreshold; public static ConfigEntry StaminaStopThreshold; public static ConfigEntry StaminaResumeFraction; public static ConfigEntry FishingCastHoldSeconds; public static ConfigEntry FishingCastConfirmTimeoutSeconds; public static ConfigEntry FishingReelTimeoutSeconds; public static ConfigEntry FishingPostReelCooldownSeconds; public static ConfigEntry SwimBackwardStaminaFraction; public static bool SpamActive; private static readonly ActionPulse AttackPulse = new ActionPulse(); private static readonly ActionPulse BlockPulse = new ActionPulse(); private static readonly ActionPulse JumpPulse = new ActionPulse(); private static readonly ActionPulse UsePulse = new ActionPulse(); private static readonly ActionPulse CheerPulse = new ActionPulse(); private static readonly ActionPulse BuildPulse = new ActionPulse(); private static bool runStaminaPaused; private static bool sneakStaminaPaused; private static bool jumpStaminaPaused; private static bool cultivatorStaminaPaused; private static bool sneakCrouchSent; private static bool swimGoingBackward; private static bool eitrPaused; private static FishingPhase fishingPhase = FishingPhase.WaitingFullStamina; private static float fishingCastTimer; private static float fishingCastConfirmTimer; private static float fishingReelTimer; private static float fishingPostReelCooldownTimer; private static bool fishingFloatSeen; private static bool fishingCastJustStarted; private static bool fishingCastConfirmedMessageSeen; private static volatile bool fishingFloatDestroyedSignal; private static FishingFloat currentFishingFloat; private static FieldInfo fishingFloatLineLengthField; private static FieldInfo hoveringField; private static MethodInfo interactMethod; private static MethodInfo getRightItemMethod; public static bool IsFishingCasting { get { if (DoFishing.Value) { return fishingPhase == FishingPhase.Casting; } return false; } } public static bool IsFishingReeling { get { if (DoFishing.Value) { return fishingPhase == FishingPhase.Reeling; } return false; } } public static bool IsFishingCastPressFrame { get { if (IsFishingCasting) { return fishingCastJustStarted; } return false; } } private static string T(string ru, string en) { if (Language == null || Language.Value != SpamModLanguage.English) { return ru; } return en; } private static string ModeName((ConfigEntry entry, string nameRu, string nameEn) t) { return T(t.nameRu, t.nameEn); } private void Awake() { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown //IL_0555: Unknown result type (might be due to invalid IL or missing references) //IL_055f: Expected O, but got Unknown //IL_05a3: Unknown result type (might be due to invalid IL or missing references) //IL_05ad: Expected O, but got Unknown //IL_05f1: Unknown result type (might be due to invalid IL or missing references) //IL_05fb: Expected O, but got Unknown //IL_063f: Unknown result type (might be due to invalid IL or missing references) //IL_0649: Expected O, but got Unknown //IL_068d: Unknown result type (might be due to invalid IL or missing references) //IL_0697: Expected O, but got Unknown //IL_06db: Unknown result type (might be due to invalid IL or missing references) //IL_06e5: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Language = ((BaseUnityPlugin)this).Config.Bind("0. Язык / Language", "Язык / Language", SpamModLanguage.English, "Язык интерфейса мода (названия и описания настроек, сообщения на экране). Полностью применяется после перезапуска игры.\n\nInterface language for this mod (setting names/descriptions, on-screen messages). Fully applies after restarting the game."); TriggerKey = ((BaseUnityPlugin)this).Config.Bind(T("1. Кнопка", "1. Button"), T("Триггер-клавиша", "Trigger key"), (KeyCode)326, T("Клавиша, которая включает/выключает спам выбранного действия. По умолчанию — боковая кнопка мыши \"назад\" (Mouse3). Shift + эта клавиша переключает, какое действие из блока 2 активно.", "Key that turns the spam of the selected action on/off. Default is the mouse side \"back\" button (Mouse3). Shift + this key cycles through the actions in group 2.")); Interval = ((BaseUnityPlugin)this).Config.Bind(T("1. Кнопка", "1. Button"), T("Интервал спама (сек)", "Spam interval (sec)"), 0.1f, new ConfigDescription(T("Пауза между повторами действия", "Pause between action repeats"), (AcceptableValueBase)(object)new AcceptableValueRange(0.02f, 2f), Array.Empty())); ShowIndicator = ((BaseUnityPlugin)this).Config.Bind(T("1. Кнопка", "1. Button"), T("Показывать индикатор на экране", "Show on-screen indicator"), true, T("Отображать надпись в углу экрана, когда спам активен", "Show a label in the corner of the screen while spam is active")); string text = T("2. Действия", "2. Actions"); string text2 = "Атака"; string text3 = "Attack"; DoAttack = ((BaseUnityPlugin)this).Config.Bind(text, T(text2, text3), false, T("Спамить атаку", "Spam attack")); string text4 = "Блок"; string text5 = "Block"; DoBlock = ((BaseUnityPlugin)this).Config.Bind(text, T(text4, text5), false, T("Спамить блок. Режим сплошного удержания вместо спама включается опцией \"Блок: удерживать\" (группа 3).", "Spam block. Continuous hold instead of spamming is enabled via \"Block: hold\" (group 3).")); string text6 = "Прыжок"; string text7 = "Jump"; DoJump = ((BaseUnityPlugin)this).Config.Bind(text, T(text6, text7), false, T("Спамить прыжок. Пауза/возобновление по порогам выносливости из группы 3.", "Spam jump. Pauses/resumes based on the stamina thresholds in group 3.")); string text8 = "Взаимодействовать"; string text9 = "Interact"; DoUse = ((BaseUnityPlugin)this).Config.Bind(text, T(text8, text9), true, T("Спамит взаимодействие с объектом под прицелом (дверь, сундук, NPC и т.п.). Срабатывает только когда есть цель под прицелом — это ограничение самой игры.", "Spams interaction with whatever is under the crosshair (door, chest, NPC, etc). Only works while a target is actually under the crosshair — a limitation of the game itself.")); string text10 = "Бег"; string text11 = "Run"; DoRun = ((BaseUnityPlugin)this).Config.Bind(text, T(text10, text11), false, T("Удерживает движение вперёд + спринт. Пауза/возобновление по порогам выносливости из группы 3.", "Holds forward movement + sprint. Pauses/resumes based on the stamina thresholds in group 3.")); string text12 = "Красться"; string text13 = "Sneak"; DoSneak = ((BaseUnityPlugin)this).Config.Bind(text, T(text12, text13), false, T("При активации один раз нажимает \"присесть\", затем спамит движение вперёд. Та же логика выносливости, что у Бега (группа 3).", "Presses \"crouch\" once on activation, then spams forward movement. Same stamina logic as Run (group 3).")); string text14 = "Лук"; string text15 = "Bow"; DoBow = ((BaseUnityPlugin)this).Config.Bind(text, T(text14, text15), false, T("Спамит атаку и эмоцию /cheer. Останавливается по достижении порога прочности текущего предмета (группа 3).", "Spams attack and the /cheer emote. Stops once the current item reaches the durability threshold (group 3).")); string text16 = "Культиватор/Молот"; string text17 = "Cultivator/Hammer"; DoCultivator = ((BaseUnityPlugin)this).Config.Bind(text, T(text16, text17), false, T("Спамит нажатие постройки/действия текущим предметом. Останавливается по достижении порога прочности предмета, а также приостанавливается и возобновляется по порогам выносливости (группа 3).", "Spams the build/action press with the current item. Stops once the item reaches the durability threshold, and also pauses/resumes based on the stamina thresholds (group 3).")); string text18 = "Рыбалка"; string text19 = "Fishing"; DoFishing = ((BaseUnityPlugin)this).Config.Bind(text, T(text18, text19), false, T("Цикл: заброс (удержание атаки) -> ожидание приводнения -> сматывание (удержание блока) -> пауза до восстановления выносливости -> новый заброс. Параметры цикла — в группе 3.", "Cycle: cast (hold attack) -> wait for the line to land -> reel in (hold block) -> pause until stamina recovers -> new cast. Cycle timings are in group 3.")); string text20 = "Плавание"; string text21 = "Swim"; DoSwim = ((BaseUnityPlugin)this).Config.Bind(text, T(text20, text21), false, T("Пока персонаж плывёт — держит движение вперёд. Когда выносливость падает до порога (группа 3, по умолчанию 20%) — разворачивает движение назад. После выхода из состояния плавания (доплыли до берега/дна) — полная остановка движения, ожидание восстановления выносливости до 100%, затем плавание вперёд возобновляется.", "While the character is swimming — holds forward movement. Once stamina drops to the threshold (group 3, 20% by default) — switches to backward movement. After leaving the swimming state (reached shore/ground) — movement fully stops, waits for stamina to reach 100%, then forward swimming resumes.")); string text22 = "Эйтр"; string text23 = "Eitr"; DoEitr = ((BaseUnityPlugin)this).Config.Bind(text, T(text22, text23), false, T("Удерживает атаку. Если Эйтр падает ниже 5 — удержание прекращается. После восстановления до 50% текущего максимального Эйтра удержание автоматически возобновляется.", "Holds attack. If Eitr drops below 5 — the hold stops. It automatically resumes once Eitr recovers to 50% of the current maximum.")); modeToggles = new(ConfigEntry, string, string)[11] { (DoAttack, text2, text3), (DoBlock, text4, text5), (DoJump, text6, text7), (DoUse, text8, text9), (DoRun, text10, text11), (DoSneak, text12, text13), (DoBow, text14, text15), (DoCultivator, text16, text17), (DoFishing, text18, text19), (DoSwim, text20, text21), (DoEitr, text22, text23) }; string text24 = T("3. Параметры особых режимов", "3. Special mode settings"); BlockHoldOverride = ((BaseUnityPlugin)this).Config.Bind(text24, T("Блок: удерживать (а не спамить)", "Block: hold (instead of spam)"), false, T("По умолчанию блок спамится так же, как другие действия. Если включить — блок будет сплошным удержанием, пока активен триггер.", "By default block is spammed like other actions. If enabled — block will be held continuously while the trigger is active.")); DurabilityStopThreshold = ((BaseUnityPlugin)this).Config.Bind(text24, T("Лук/Культиватор/Молот: порог остановки по прочности", "Bow/Cultivator/Hammer: durability stop threshold"), 2f, T("Если прочность текущего предмета опускается до этого значения или ниже — спам атаки автоматически останавливается.", "If the current item's durability drops to this value or below — attack spam stops automatically.")); StaminaStopThreshold = ((BaseUnityPlugin)this).Config.Bind(text24, T("Выносливость: порог остановки", "Stamina: stop threshold"), 5f, T("Если текущая выносливость опускается ниже этого значения — Бег/Красться/Прыжок/Культиватор/Молот приостанавливаются.", "If current stamina drops below this value — Run/Sneak/Jump/Cultivator/Hammer pause.")); StaminaResumeFraction = ((BaseUnityPlugin)this).Config.Bind(text24, T("Выносливость: доля для возобновления", "Stamina: resume fraction"), 0.5f, new ConfigDescription(T("Доля от текущей максимальной выносливости, при восстановлении до которой Бег/Красться/Прыжок/Культиватор/Молот возобновляются.", "Fraction of the current maximum stamina at which Run/Sneak/Jump/Cultivator/Hammer resume."), (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 1f), Array.Empty())); FishingCastHoldSeconds = ((BaseUnityPlugin)this).Config.Bind(text24, T("Рыбалка: удержание заброса (сек)", "Fishing: cast hold time (sec)"), 1f, new ConfigDescription(T("Как долго удерживать атаку при забросе лески", "How long to hold attack when casting the line"), (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 3f), Array.Empty())); FishingCastConfirmTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind(text24, T("Рыбалка: таймаут ожидания приводнения (сек)", "Fishing: cast-landed wait timeout (sec)"), 3f, new ConfigDescription(T("После отпускания заброса ждём сообщение об уходе лески в воду (счётчик метров), прежде чем начать сматывание блоком — иначе блок гасит бросок. Если сообщение не поймано за это время (fallback), сматывание всё равно запустится по таймауту.", "After releasing the cast we wait for the \"line landed\" message (the meter counter) before reeling with block — otherwise block would cancel the cast. If the message isn't caught in time (fallback), reeling starts anyway after the timeout."), (AcceptableValueBase)(object)new AcceptableValueRange(1f, 10f), Array.Empty())); FishingReelTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind(text24, T("Рыбалка: safety-таймаут сматывания (сек)", "Fishing: reel safety timeout (sec)"), 20f, new ConfigDescription(T("Подстраховка на случай сбоя определения конца сматывания (например, поплавок не заспавнился): по истечении этого времени сматывание принудительно завершится, чтобы не застрять в цикле. В норме сессия завершается раньше — по факту уничтожения поплавка (полная смотка / срыв рыбы / обрыв лески).", "A fallback in case reel-end detection fails (e.g. the float never spawned): reeling forcibly ends after this time so the cycle doesn't get stuck. Normally the session ends earlier, once the float is actually destroyed (full reel / fish escaped / line broke)."), (AcceptableValueBase)(object)new AcceptableValueRange(5f, 60f), Array.Empty())); FishingPostReelCooldownSeconds = ((BaseUnityPlugin)this).Config.Bind(text24, T("Рыбалка: пауза после смотки перед новым забросом (сек)", "Fishing: post-reel pause before new cast (sec)"), 0.4f, new ConfigDescription(T("Небольшая пауза между окончанием смотки (отпусканием блока) и следующим нажатием заброса. Без неё игра иногда не успевает обработать снятие блока до того, как мод пошлёт нажатие атаки для нового заброса — первое нажатие пропадает впустую (леска не летит), и фаза заброса срывается.", "A small pause between the end of reeling (releasing block) and the next cast press. Without it the game sometimes hasn't finished processing the block release before the mod sends the attack press for a new cast — the first press is wasted (the line doesn't fly) and the cast phase breaks."), (AcceptableValueBase)(object)new AcceptableValueRange(0f, 2f), Array.Empty())); SwimBackwardStaminaFraction = ((BaseUnityPlugin)this).Config.Bind(text24, T("Плавание: доля выносливости для разворота назад", "Swim: backward-turn stamina fraction"), 0.2f, new ConfigDescription(T("Пока активно 'Плавание': пока выносливость выше этой доли от максимума — плывём вперёд. Как только выносливость опускается до этой доли (по умолчанию 0.2 = 20%) или ниже — разворачиваемся и плывём назад. Разворот держится, пока не произойдёт выход из состояния плавания и полное восстановление выносливости.", "While 'Swim' is active: as long as stamina is above this fraction of the maximum — swim forward. Once stamina drops to this fraction (0.2 = 20% by default) or below — turn around and swim backward. The turn holds until the swimming state ends and stamina fully recovers."), (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 0.95f), Array.Empty())); hoveringField = typeof(Player).GetField("m_hovering", BindingFlags.Instance | BindingFlags.NonPublic); interactMethod = typeof(Player).GetMethod("Interact", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[3] { typeof(GameObject), typeof(bool), typeof(bool) }, null); if (hoveringField == null || interactMethod == null) { Log.LogError((object)T("[SpamKeyMod] Не найдены ожидаемые члены класса Player — вероятно, версия игры отличается. Функция 'Взаимодействовать' может не работать.", "[SpamKeyMod] Expected Player class members not found — the game version likely differs. The 'Interact' feature may not work.")); } getRightItemMethod = AccessTools.Method(typeof(Humanoid), "GetRightItem", (Type[])null, (Type[])null); if (getRightItemMethod == null) { Log.LogError((object)T("[SpamKeyMod] Не найден метод Humanoid.GetRightItem() — проверка прочности культиватора может не работать.", "[SpamKeyMod] Method Humanoid.GetRightItem() not found — the cultivator durability check may not work.")); } fishingFloatLineLengthField = AccessTools.Field(typeof(FishingFloat), "m_lineLength"); if (fishingFloatLineLengthField == null) { Log.LogError((object)T("[SpamKeyMod] Не найдено поле FishingFloat.m_lineLength — принудительное завершение сматывания без рыбы может не работать (останется резервный safety-таймаут).", "[SpamKeyMod] Field FishingFloat.m_lineLength not found — forced end of an empty reel may not work (the safety timeout fallback will still apply).")); } Harmony.CreateAndPatchAll(typeof(SpamKeyModPlugin).Assembly, "Shinsu.spamkeymod"); } private void OnGUI() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) if (ShowIndicator.Value && SpamActive) { GUIStyle val = new GUIStyle { fontSize = 20, fontStyle = (FontStyle)1, alignment = (TextAnchor)0 }; val.normal.textColor = Color.red; GUIStyle val2 = new GUIStyle(val); val2.normal.textColor = Color.black; string text = string.Join(", ", modeToggles.Where(((ConfigEntry entry, string nameRu, string nameEn) t) => t.entry.Value).Select(ModeName)); if (string.IsNullOrEmpty(text)) { text = "—"; } string text2 = "SKM:ON [" + text + "]"; Rect val3 = default(Rect); ((Rect)(ref val3))..ctor(20f, 20f, 500f, 40f); GUI.Label(new Rect(((Rect)(ref val3)).x + 2f, ((Rect)(ref val3)).y + 2f, ((Rect)(ref val3)).width, ((Rect)(ref val3)).height), text2, val2); GUI.Label(val3, text2, val); } } private void Update() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) fishingCastJustStarted = false; if ((Object)(object)Player.m_localPlayer == (Object)null) { SpamActive = false; return; } if (Input.GetKeyDown(TriggerKey.Value)) { if (Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303)) { CycleMode(); } else { SpamActive = !SpamActive; } } if (!SpamActive) { AttackPulse.Reset(); BlockPulse.Reset(); JumpPulse.Reset(); UsePulse.Reset(); CheerPulse.Reset(); runStaminaPaused = false; sneakStaminaPaused = false; jumpStaminaPaused = false; cultivatorStaminaPaused = false; sneakCrouchSent = false; swimGoingBackward = false; eitrPaused = false; fishingPhase = FishingPhase.WaitingFullStamina; fishingFloatSeen = false; fishingFloatDestroyedSignal = false; currentFishingFloat = null; fishingPostReelCooldownTimer = 0f; BuildPulse.Reset(); return; } float deltaTime = Time.deltaTime; AttackPulse.Tick(deltaTime); BlockPulse.Tick(deltaTime); JumpPulse.Tick(deltaTime); if (DoUse.Value) { UsePulse.Tick(deltaTime); if (UsePulse.ConsumeTick(Interval.Value)) { TryInteract(); } } if (DoBow.Value) { HandleBowMode(deltaTime); } if (DoCultivator.Value) { HandleCultivatorMode(deltaTime); } if (DoFishing.Value) { HandleFishingMode(deltaTime); } else { fishingPhase = FishingPhase.WaitingFullStamina; fishingFloatSeen = false; fishingFloatDestroyedSignal = false; currentFishingFloat = null; fishingPostReelCooldownTimer = 0f; } if (!DoSneak.Value) { sneakCrouchSent = false; } if (!DoSwim.Value) { swimGoingBackward = false; } } private static void CycleMode() { int num = (Array.FindIndex(modeToggles, ((ConfigEntry entry, string nameRu, string nameEn) t) => t.entry.Value) + 1) % modeToggles.Length; (ConfigEntry, string, string)[] array = modeToggles; for (int num2 = 0; num2 < array.Length; num2++) { array[num2].Item1.Value = false; } modeToggles[num].entry.Value = true; ShowCenterMessage(T("Режим спама", "Spam mode") + ": " + ModeName(modeToggles[num])); } private static void ShowCenterMessage(string text) { if ((Object)(object)MessageHud.instance == (Object)null) { return; } try { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false); } catch (Exception ex) { Log.LogError((object)("[SpamKeyMod] " + T("Ошибка показа сообщения", "Error showing message") + ": " + ex.Message)); } } private static bool CheckDurabilityStop(string itemLabel, ItemData item) { if (item == null || item.m_durability > DurabilityStopThreshold.Value) { return false; } SpamActive = false; AttackPulse.Reset(); CheerPulse.Reset(); BuildPulse.Reset(); ShowCenterMessage(itemLabel + ": " + T("прочность критична", "durability critical") + " " + string.Format("({0:0.0}) — {1}", item.m_durability, T("спам остановлен", "spam stopped"))); return true; } private static ItemData GetPlayerRightItem() { if ((Object)(object)Player.m_localPlayer == (Object)null || getRightItemMethod == null) { return null; } object? obj = getRightItemMethod.Invoke(Player.m_localPlayer, null); return (ItemData)((obj is ItemData) ? obj : null); } private static void HandleCultivatorMode(float dt) { ItemData playerRightItem = GetPlayerRightItem(); if (!CheckDurabilityStop(T("Культиватор/Молот", "Cultivator/Hammer"), playerRightItem) && GetCultivatorAllowed()) { BuildPulse.Tick(dt); } } public static bool ConsumeCultivatorBuildPulse() { return ShouldPulse(BuildPulse); } private static void HandleBowMode(float dt) { if (!CheckDurabilityStop(T("Лук", "Bow"), ((Humanoid)Player.m_localPlayer).GetCurrentWeapon())) { CheerPulse.Tick(dt); if (CheerPulse.ConsumeTick(Interval.Value)) { TryCheer(); } } } private static void HandleFishingMode(float dt) { switch (fishingPhase) { case FishingPhase.WaitingFullStamina: fishingPostReelCooldownTimer += dt; if (IsStaminaFull() && fishingPostReelCooldownTimer >= FishingPostReelCooldownSeconds.Value) { fishingPhase = FishingPhase.Casting; fishingCastTimer = 0f; fishingCastJustStarted = true; } break; case FishingPhase.Casting: fishingCastTimer += dt; if (fishingCastTimer >= FishingCastHoldSeconds.Value) { fishingPhase = FishingPhase.WaitingCastConfirm; fishingCastConfirmTimer = 0f; fishingCastConfirmedMessageSeen = false; } break; case FishingPhase.WaitingCastConfirm: fishingCastConfirmTimer += dt; if (fishingCastConfirmedMessageSeen || fishingCastConfirmTimer >= FishingCastConfirmTimeoutSeconds.Value) { fishingPhase = FishingPhase.Reeling; fishingReelTimer = 0f; fishingFloatSeen = false; fishingFloatDestroyedSignal = false; currentFishingFloat = null; } break; case FishingPhase.Reeling: { fishingReelTimer += dt; if ((Object)(object)currentFishingFloat == (Object)null) { currentFishingFloat = FindOwnFishingFloat(); } bool flag = (Object)(object)currentFishingFloat == (Object)null; float? num = null; bool flag2 = false; if (!flag && fishingFloatLineLengthField != null) { try { num = (float)fishingFloatLineLengthField.GetValue(currentFishingFloat); flag2 = (Object)(object)currentFishingFloat.GetCatch() != (Object)null; } catch (Exception) { flag = true; } } if (!flag) { fishingFloatSeen = true; } bool flag3 = !flag2 && num.HasValue && num.Value <= 0.55f; if (flag3 && !flag) { try { ZNetView component = ((Component)currentFishingFloat).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { component.Destroy(); } } catch (Exception ex2) { Log.LogError((object)("[SpamKeyMod] " + T("Ошибка принудительного завершения смотки", "Error forcing reel end") + ": " + ex2.Message)); } } if (fishingFloatDestroyedSignal || flag3 || (fishingFloatSeen && flag) || fishingReelTimer >= FishingReelTimeoutSeconds.Value) { fishingPhase = FishingPhase.WaitingFullStamina; fishingFloatSeen = false; fishingFloatDestroyedSignal = false; currentFishingFloat = null; fishingPostReelCooldownTimer = 0f; } break; } } } private static FishingFloat FindOwnFishingFloat() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null) { return null; } ZDOID zDOID = ((Character)Player.m_localPlayer).GetZDOID(); long userID = ((ZDOID)(ref zDOID)).UserID; foreach (FishingFloat allInstance in FishingFloat.GetAllInstances()) { if (!((Object)(object)allInstance == (Object)null)) { ZNetView component = ((Component)allInstance).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid() && component.GetZDO().GetLong(ZDOVars.s_rodOwner, 0L) == userID) { return allInstance; } } } return null; } public static void NotifyFishingCastLanded() { fishingCastConfirmedMessageSeen = true; } public static void NotifyFishingFloatDestroyed() { fishingFloatDestroyedSignal = true; } private static bool IsStaminaFull() { if ((Object)(object)Player.m_localPlayer == (Object)null) { return false; } float stamina = Player.m_localPlayer.GetStamina(); float maxStamina = ((Character)Player.m_localPlayer).GetMaxStamina(); return stamina >= maxStamina * 0.99f; } private static bool IsStaminaGateOpen(ref bool pausedFlag) { if ((Object)(object)Player.m_localPlayer == (Object)null) { return false; } float stamina = Player.m_localPlayer.GetStamina(); float maxStamina = ((Character)Player.m_localPlayer).GetMaxStamina(); if (!pausedFlag && stamina < StaminaStopThreshold.Value) { pausedFlag = true; } else if (pausedFlag && stamina >= maxStamina * StaminaResumeFraction.Value) { pausedFlag = false; } return !pausedFlag; } public static bool GetRunAllowed() { return IsStaminaGateOpen(ref runStaminaPaused); } public static bool GetSneakMoveAllowed() { return IsStaminaGateOpen(ref sneakStaminaPaused); } public static bool GetCultivatorAllowed() { return IsStaminaGateOpen(ref cultivatorStaminaPaused); } private static bool IsEitrGateOpen(ref bool pausedFlag) { if ((Object)(object)Player.m_localPlayer == (Object)null) { return false; } float eitr = Player.m_localPlayer.GetEitr(); float maxEitr = ((Character)Player.m_localPlayer).GetMaxEitr(); if (!pausedFlag && eitr < 5f) { pausedFlag = true; } else if (pausedFlag && eitr >= maxEitr * 0.5f) { pausedFlag = false; } return !pausedFlag; } public static bool GetEitrAllowed() { return IsEitrGateOpen(ref eitrPaused); } public static bool ConsumeSneakCrouchOnce() { if (sneakCrouchSent) { return false; } sneakCrouchSent = true; return true; } public static void ApplySwimControls(ref Vector3 movedir, ref bool run) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null) { return; } bool flag = ((Character)Player.m_localPlayer).IsSwimming(); float stamina = Player.m_localPlayer.GetStamina(); float maxStamina = ((Character)Player.m_localPlayer).GetMaxStamina(); if (swimGoingBackward) { if (!flag) { movedir = Vector3.zero; run = false; if (IsStaminaFull()) { swimGoingBackward = false; } return; } if (IsStaminaFull()) { swimGoingBackward = false; } } else if (flag && maxStamina > 0f && stamina <= maxStamina * SwimBackwardStaminaFraction.Value) { swimGoingBackward = true; } movedir = (swimGoingBackward ? new Vector3(0f, 0f, -1f) : new Vector3(0f, 0f, 1f)); run = false; } private static void TryCheer() { if ((Object)(object)Player.m_localPlayer == (Object)null) { return; } try { Emote.DoEmote((Emotes)3); } catch (Exception ex) { Log.LogError((object)("[SpamKeyMod] " + T("Ошибка вызова эмоции /cheer", "Error triggering the /cheer emote") + ": " + ex.Message)); } } private static void TryInteract() { if (interactMethod == null || hoveringField == null) { return; } object? value = hoveringField.GetValue(Player.m_localPlayer); GameObject val = (GameObject)((value is GameObject) ? value : null); if ((Object)(object)val == (Object)null) { return; } try { interactMethod.Invoke(Player.m_localPlayer, new object[3] { val, false, false }); } catch (Exception ex) { Log.LogError((object)("[SpamKeyMod] " + T("Ошибка вызова Interact", "Error calling Interact") + ": " + ex.Message)); } } private static bool ShouldPulse(ActionPulse pulse) { return pulse.ConsumeTick(Interval.Value); } public static bool GetAttackState() { return ShouldPulse(AttackPulse); } public static bool GetBlockState() { if (!BlockHoldOverride.Value) { return ShouldPulse(BlockPulse); } return SpamActive; } public static bool GetJumpState() { if (!IsStaminaGateOpen(ref jumpStaminaPaused)) { return false; } return ShouldPulse(JumpPulse); } } [HarmonyPatch(typeof(Player), "SetControls")] public static class Player_SetControls_Patch { private static void Prefix(ref Vector3 movedir, ref bool attack, ref bool attackHold, ref bool block, ref bool blockHold, ref bool jump, ref bool run, ref bool crouch) { if (!SpamKeyModPlugin.SpamActive) { return; } if (SpamKeyModPlugin.DoAttack.Value || SpamKeyModPlugin.DoBow.Value) { bool attackState = SpamKeyModPlugin.GetAttackState(); attack |= attackState; attackHold |= attackState; } if (SpamKeyModPlugin.DoEitr.Value) { bool eitrAllowed = SpamKeyModPlugin.GetEitrAllowed(); attackHold |= eitrAllowed; if (eitrAllowed) { attack = true; } } if (SpamKeyModPlugin.DoFishing.Value && SpamKeyModPlugin.IsFishingCasting) { attack = SpamKeyModPlugin.IsFishingCastPressFrame; attackHold = true; } if (SpamKeyModPlugin.DoBlock.Value) { bool blockState = SpamKeyModPlugin.GetBlockState(); block |= blockState; blockHold |= blockState; } if (SpamKeyModPlugin.DoFishing.Value && SpamKeyModPlugin.IsFishingReeling) { block = true; blockHold = true; } if (SpamKeyModPlugin.DoJump.Value) { bool jumpState = SpamKeyModPlugin.GetJumpState(); jump |= jumpState; } if (SpamKeyModPlugin.DoRun.Value && SpamKeyModPlugin.GetRunAllowed()) { movedir.z = 1f; run = true; } if (SpamKeyModPlugin.DoSneak.Value) { bool flag = SpamKeyModPlugin.ConsumeSneakCrouchOnce(); crouch |= flag; if (SpamKeyModPlugin.GetSneakMoveAllowed()) { movedir.z = 1f; } } if (SpamKeyModPlugin.DoSwim.Value) { SpamKeyModPlugin.ApplySwimControls(ref movedir, ref run); } } } [HarmonyPatch(typeof(MessageHud), "ShowMessage")] public static class MessageHud_ShowMessage_Patch { private static readonly Regex MeterMessageRegex = new Regex("^\\d+m$"); private static void Postfix(MessageType type, string text) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 if (SpamKeyModPlugin.DoFishing.Value && (int)type == 2 && !string.IsNullOrEmpty(text) && MeterMessageRegex.IsMatch(text)) { SpamKeyModPlugin.NotifyFishingCastLanded(); } } } [HarmonyPatch(typeof(Player), "UpdatePlacement")] public static class Player_UpdatePlacement_Patch { private static readonly FieldInfo PlacePressedTime = AccessTools.Field(typeof(Player), "m_placePressedTime"); private static void Prefix(Player __instance) { if (SpamKeyModPlugin.SpamActive && SpamKeyModPlugin.DoCultivator.Value && SpamKeyModPlugin.ConsumeCultivatorBuildPulse()) { PlacePressedTime.SetValue(__instance, Time.time); } } } [HarmonyPatch(typeof(FishingFloat), "OnDestroy")] public static class FishingFloat_OnDestroy_Patch { private static void Prefix(FishingFloat __instance) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (!SpamKeyModPlugin.DoFishing.Value || (Object)(object)Player.m_localPlayer == (Object)null) { return; } ZNetView component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { long num = component.GetZDO().GetLong(ZDOVars.s_rodOwner, 0L); ZDOID zDOID = ((Character)Player.m_localPlayer).GetZDOID(); if (num == ((ZDOID)(ref zDOID)).UserID) { SpamKeyModPlugin.NotifyFishingFloatDestroyed(); } } } }