From 6c9f57eae1bb08f099c5396a702a2b6ff3465f01 Mon Sep 17 00:00:00 2001 From: Justin Oros Date: Tue, 15 Sep 2026 18:52:28 -0700 Subject: [PATCH] Toggle modes only with a gun, burst fires 3 per click --- FullAuto.cs | 132 +++++++++++++++++++++++++++++++++++++++++++++------- README.md | 41 +++++++++------- 2 files changed, 138 insertions(+), 35 deletions(-) diff --git a/FullAuto.cs b/FullAuto.cs index 94be5b1..4ae2c6e 100644 --- a/FullAuto.cs +++ b/FullAuto.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using HarmonyLib; @@ -11,6 +12,13 @@ using UnityEngine; namespace FullAuto; +public enum FireMode +{ + Auto, + Burst, + Semi +} + public class FullAuto : SonsMod { internal const float MinRpm = 60f; @@ -18,6 +26,7 @@ public class FullAuto : SonsMod internal static bool Enabled = true; internal static float Rpm = 900f; + internal static FireMode Mode = FireMode.Auto; internal static bool Verbose; private static string _configPath; @@ -25,13 +34,36 @@ public class FullAuto : SonsMod public FullAuto() { HarmonyPatchAll = true; + OnUpdateCallback = OnUpdate; } protected override void OnSdkInitialized() { _configPath = Path.Combine(LoaderEnvironment.UserDataDirectory, "FullAuto.txt"); Load(); - RLog.Msg($"FullAuto loaded. Enabled: {Enabled}, RPM: {Rpm}. Guns only. Console: fullauto"); + RLog.Msg($"FullAuto loaded. RPM: {Rpm}. Guns only. Middle mouse cycles modes. Console: fullauto"); + } + + private void OnUpdate() + { + if (!Enabled || !GameState.IsPlayerControllable || !CheckFireInputPatch.HoldingGun) + return; + + if (Mode == FireMode.Burst && Input.GetMouseButtonDown(0)) + CheckFireInputPatch.StartBurst(); + + if (Input.GetMouseButtonDown(2)) + { + Mode = Mode switch + { + FireMode.Auto => FireMode.Burst, + FireMode.Burst => FireMode.Semi, + _ => FireMode.Auto + }; + CheckFireInputPatch.CancelBurst(); + SonsTools.ShowMessage(ModeName(), 2f); + RLog.Msg($"FullAuto mode: {ModeName()}"); + } } [DebugCommand("fullauto")] @@ -65,6 +97,7 @@ public class FullAuto : SonsMod if (parts.Length > 1 && float.TryParse(parts[1], out var value)) { Rpm = Mathf.Clamp(value, MinRpm, MaxRpm); + Save(); break; } Usage(); @@ -74,7 +107,7 @@ public class FullAuto : SonsMod return; } - Save(); + CheckFireInputPatch.CancelBurst(); Announce(); } @@ -85,9 +118,19 @@ public class FullAuto : SonsMod RLog.Msg(text); } + private static string ModeName() + { + return Mode switch + { + FireMode.Auto => "Full-Auto", + FireMode.Burst => "3-Round Burst", + _ => "Semi-Automatic" + }; + } + internal static void Announce() { - var text = $"FullAuto {(Enabled ? "ON" : "OFF")} ({Rpm} RPM)"; + var text = Enabled ? $"{ModeName()} ({Rpm} RPM)" : "FullAuto OFF"; SonsTools.ShowMessage(text); RLog.Msg(text); } @@ -108,9 +151,7 @@ public class FullAuto : SonsMod var key = kv[0].Trim().ToLowerInvariant(); var val = kv[1].Trim(); - if (key == "enabled" && bool.TryParse(val, out var enabled)) - Enabled = enabled; - else if (key == "rpm" && float.TryParse(val, out var rpm)) + if (key == "rpm" && float.TryParse(val, out var rpm)) Rpm = Mathf.Clamp(rpm, MinRpm, MaxRpm); } } @@ -124,7 +165,7 @@ public class FullAuto : SonsMod { try { - File.WriteAllText(_configPath, $"enabled={Enabled}\nrpm={Rpm}\n"); + File.WriteAllText(_configPath, $"rpm={Rpm}\n"); } catch (Exception e) { @@ -137,6 +178,7 @@ public class FullAuto : SonsMod internal static class CheckFireInputPatch { private const int MaxShotsPerFrame = 4; + private const int BurstSize = 3; private static readonly HashSet Guns = new() { @@ -149,8 +191,29 @@ internal static class CheckFireInputPatch private static readonly Dictionary Allowed = new(); private static readonly HashSet Reported = new(); private static float _nextShot; + private static int _burstLeft; + private static float _lastGunTime = -1f; private static bool _loggedHook; + internal static bool HoldingGun => _lastGunTime >= 0f && Time.time - _lastGunTime < 0.25f; + + internal static void CancelBurst() + { + _burstLeft = 0; + } + + internal static void StartBurst() + { + if (_burstLeft > 0) + return; + + _burstLeft = BurstSize; + _nextShot = 0f; + + if (FullAuto.Verbose) + RLog.Msg("FullAuto burst start"); + } + private static bool Prefix(RangedWeaponController __instance) { if (!_loggedHook) @@ -159,25 +222,49 @@ internal static class CheckFireInputPatch RLog.Msg("FullAuto: CheckFireInput hook is firing"); } - if (!FullAuto.Enabled || !__instance.IsLocalPlayer() || !IsAllowed(__instance)) + if (!__instance.IsLocalPlayer()) return true; - if (!Input.GetMouseButton(0) || !GameState.IsPlayerControllable) + var allowed = IsAllowed(__instance); + _lastGunTime = allowed ? Time.time : -1f; + + if (!FullAuto.Enabled || !allowed) return true; + if (FullAuto.Mode == FireMode.Semi) + return true; + + if (!GameState.IsPlayerControllable) + { + CancelBurst(); + return true; + } + + var held = Input.GetMouseButton(0); + + if (FullAuto.Mode == FireMode.Burst) + { + if (_burstLeft <= 0) + return !held; + } + else if (!held) + { + return true; + } + if (__instance._isReloading || __instance.IsReloading()) - return true; + return Stop(); if (__instance._mustAimToFire && !__instance.IsAiming) - return true; + return Stop(); var weapon = __instance.GetRangedWeapon(); if (!weapon) - return true; + return Stop(); var ammo = weapon.GetAmmo(); if (ammo == null || ammo.GetRemainingAmmo() <= 0) - return true; + return Stop(); var now = Time.time; var interval = 60f / FullAuto.Rpm; @@ -188,6 +275,13 @@ internal static class CheckFireInputPatch var shots = 0; while (_nextShot <= now && shots < MaxShotsPerFrame && ammo.GetRemainingAmmo() > 0) { + if (FullAuto.Mode == FireMode.Burst) + { + if (_burstLeft <= 0) + break; + _burstLeft--; + } + Fire(__instance, ammo); _nextShot += interval; shots++; @@ -196,6 +290,12 @@ internal static class CheckFireInputPatch return false; } + private static bool Stop() + { + _burstLeft = 0; + return true; + } + private static void Fire(RangedWeaponController controller, RangedWeapon.Ammo ammo) { var before = ammo.GetRemainingAmmo(); @@ -220,7 +320,7 @@ internal static class CheckFireInputPatch RLog.Msg($"FullAuto first shot with {name}: ammo {before} -> {after}, audio {controller._playFireAudio}"); if (FullAuto.Verbose) - RLog.Msg($"FullAuto shot {name}: ammo {before} -> {after}"); + RLog.Msg($"FullAuto shot {name} ({FullAuto.Mode}): ammo {before} -> {after}"); } private static bool IsAllowed(RangedWeaponController controller) @@ -235,6 +335,4 @@ internal static class CheckFireInputPatch RLog.Msg($"FullAuto {name}: {(allowed ? "full-auto" : "ignored")}"); return allowed; } -} - - +} \ No newline at end of file diff --git a/README.md b/README.md index 57c4187..73223a2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # FullAuto -Hold the fire button to shoot any gun full-auto in Sons of the Forest. +Hold the fire button to shoot guns full-auto in Sons of the Forest. -Works with the pistol, revolver, shotgun, rifle and other guns. The fire rate is +Works with the pistol, revolver, shotgun and rifle. The fire rate is adjustable and your settings are saved between sessions. ## Multiplayer @@ -43,30 +43,36 @@ Mods\FullAuto\manifest.json Equip a gun and hold the fire button. -| Control | Action | -| --- | --- | -| F8 | Toggle the mod on or off | -| F1 | Open the console | +With a gun equipped, click the middle mouse button to cycle fire modes: -Console commands: +| Mode | Behavior | +| --- | --- | +| Full-auto | Fires while the button is held | +| 3-round burst | Fires 3 shots per click | +| Semi-auto | Normal game behavior, one shot per click | + +The mod starts in full-auto every time the game loads. + +Press F1 to open the console, then use these commands: | Command | Action | | --- | --- | | `fullauto` | Show current settings | | `fullauto on` | Enable | -| `fullauto off` | Disable | -| `fullauto rpm 1200` | Set the fire rate, 60 to 6000 rounds per minute | -| `fullauto mode shoot` | Change the fire method, `shoot`, `fire` or `trigger` | +| `fullauto off` | Disable until the game restarts | +| `fullauto toggle` | Switch between on and off | +| `fullauto rpm 1200` | Set the fire rate, 60 to 3000 rounds per minute | +| `fullauto debug` | Log every shot to `_RedLoader\Latest.log` | -The default is 900 rpm with mode `shoot`. If a gun does not fire full-auto, try -another mode. +The default is 900 rpm. The rpm also sets how fast burst shots fire. -Settings are saved to `UserData\FullAuto.cfg` in your game folder. +The rpm is saved to `UserData\FullAuto.txt` in your game folder. -## Excluded weapons +## Supported weapons -The bow, slingshot, rope gun and thrown items such as grenades, molotovs, time -bombs and rocks keep their normal behavior. +Only the pistol, revolver, shotgun and rifle are affected. Bows, the crossbow, +slingshot, flare gun, stun gun, rope gun and thrown items keep their normal +behavior. ## Building from source @@ -82,5 +88,4 @@ the game is not found automatically. ## Troubleshooting -Check `_RedLoader\Latest.log` in your game folder. FullAuto logs a line when it - +Check `_RedLoader\Latest.log` in your game folder. FullAuto logs a line when it \ No newline at end of file