Toggle modes only with a gun, burst fires 3 per click

This commit is contained in:
justin committed 2026-09-15 18:52:28 -07:00
1 parent 07a93fc03b
commit 6c9f57eae1
2 files changed
+138 -35

No files matched your search

+115 -17
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using HarmonyLib; using HarmonyLib;
@@ -11,6 +12,13 @@ using UnityEngine;
namespace FullAuto; namespace FullAuto;
public enum FireMode
{
Auto,
Burst,
Semi
}
public class FullAuto : SonsMod public class FullAuto : SonsMod
{ {
internal const float MinRpm = 60f; internal const float MinRpm = 60f;
@@ -18,6 +26,7 @@ public class FullAuto : SonsMod
internal static bool Enabled = true; internal static bool Enabled = true;
internal static float Rpm = 900f; internal static float Rpm = 900f;
internal static FireMode Mode = FireMode.Auto;
internal static bool Verbose; internal static bool Verbose;
private static string _configPath; private static string _configPath;
@@ -25,13 +34,36 @@ public class FullAuto : SonsMod
public FullAuto() public FullAuto()
{ {
HarmonyPatchAll = true; HarmonyPatchAll = true;
OnUpdateCallback = OnUpdate;
} }
protected override void OnSdkInitialized() protected override void OnSdkInitialized()
{ {
_configPath = Path.Combine(LoaderEnvironment.UserDataDirectory, "FullAuto.txt"); _configPath = Path.Combine(LoaderEnvironment.UserDataDirectory, "FullAuto.txt");
Load(); 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")] [DebugCommand("fullauto")]
@@ -65,6 +97,7 @@ public class FullAuto : SonsMod
if (parts.Length > 1 && float.TryParse(parts[1], out var value)) if (parts.Length > 1 && float.TryParse(parts[1], out var value))
{ {
Rpm = Mathf.Clamp(value, MinRpm, MaxRpm); Rpm = Mathf.Clamp(value, MinRpm, MaxRpm);
Save();
break; break;
} }
Usage(); Usage();
@@ -74,7 +107,7 @@ public class FullAuto : SonsMod
return; return;
} }
Save(); CheckFireInputPatch.CancelBurst();
Announce(); Announce();
} }
@@ -85,9 +118,19 @@ public class FullAuto : SonsMod
RLog.Msg(text); 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() internal static void Announce()
{ {
var text = $"FullAuto {(Enabled ? "ON" : "OFF")} ({Rpm} RPM)"; var text = Enabled ? $"{ModeName()} ({Rpm} RPM)" : "FullAuto OFF";
SonsTools.ShowMessage(text); SonsTools.ShowMessage(text);
RLog.Msg(text); RLog.Msg(text);
} }
@@ -108,9 +151,7 @@ public class FullAuto : SonsMod
var key = kv[0].Trim().ToLowerInvariant(); var key = kv[0].Trim().ToLowerInvariant();
var val = kv[1].Trim(); var val = kv[1].Trim();
if (key == "enabled" && bool.TryParse(val, out var enabled)) if (key == "rpm" && float.TryParse(val, out var rpm))
Enabled = enabled;
else if (key == "rpm" && float.TryParse(val, out var rpm))
Rpm = Mathf.Clamp(rpm, MinRpm, MaxRpm); Rpm = Mathf.Clamp(rpm, MinRpm, MaxRpm);
} }
} }
@@ -124,7 +165,7 @@ public class FullAuto : SonsMod
{ {
try try
{ {
File.WriteAllText(_configPath, $"enabled={Enabled}\nrpm={Rpm}\n"); File.WriteAllText(_configPath, $"rpm={Rpm}\n");
} }
catch (Exception e) catch (Exception e)
{ {
@@ -137,6 +178,7 @@ public class FullAuto : SonsMod
internal static class CheckFireInputPatch internal static class CheckFireInputPatch
{ {
private const int MaxShotsPerFrame = 4; private const int MaxShotsPerFrame = 4;
private const int BurstSize = 3;
private static readonly HashSet<string> Guns = new() private static readonly HashSet<string> Guns = new()
{ {
@@ -149,8 +191,29 @@ internal static class CheckFireInputPatch
private static readonly Dictionary<IntPtr, bool> Allowed = new(); private static readonly Dictionary<IntPtr, bool> Allowed = new();
private static readonly HashSet<string> Reported = new(); private static readonly HashSet<string> Reported = new();
private static float _nextShot; private static float _nextShot;
private static int _burstLeft;
private static float _lastGunTime = -1f;
private static bool _loggedHook; 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) private static bool Prefix(RangedWeaponController __instance)
{ {
if (!_loggedHook) if (!_loggedHook)
@@ -159,25 +222,49 @@ internal static class CheckFireInputPatch
RLog.Msg("FullAuto: CheckFireInput hook is firing"); RLog.Msg("FullAuto: CheckFireInput hook is firing");
} }
if (!FullAuto.Enabled || !__instance.IsLocalPlayer() || !IsAllowed(__instance)) if (!__instance.IsLocalPlayer())
return true; return true;
if (!Input.GetMouseButton(0) || !GameState.IsPlayerControllable) var allowed = IsAllowed(__instance);
_lastGunTime = allowed ? Time.time : -1f;
if (!FullAuto.Enabled || !allowed)
return true; 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()) if (__instance._isReloading || __instance.IsReloading())
return true; return Stop();
if (__instance._mustAimToFire && !__instance.IsAiming) if (__instance._mustAimToFire && !__instance.IsAiming)
return true; return Stop();
var weapon = __instance.GetRangedWeapon(); var weapon = __instance.GetRangedWeapon();
if (!weapon) if (!weapon)
return true; return Stop();
var ammo = weapon.GetAmmo(); var ammo = weapon.GetAmmo();
if (ammo == null || ammo.GetRemainingAmmo() <= 0) if (ammo == null || ammo.GetRemainingAmmo() <= 0)
return true; return Stop();
var now = Time.time; var now = Time.time;
var interval = 60f / FullAuto.Rpm; var interval = 60f / FullAuto.Rpm;
@@ -188,6 +275,13 @@ internal static class CheckFireInputPatch
var shots = 0; var shots = 0;
while (_nextShot <= now && shots < MaxShotsPerFrame && ammo.GetRemainingAmmo() > 0) while (_nextShot <= now && shots < MaxShotsPerFrame && ammo.GetRemainingAmmo() > 0)
{ {
if (FullAuto.Mode == FireMode.Burst)
{
if (_burstLeft <= 0)
break;
_burstLeft--;
}
Fire(__instance, ammo); Fire(__instance, ammo);
_nextShot += interval; _nextShot += interval;
shots++; shots++;
@@ -196,6 +290,12 @@ internal static class CheckFireInputPatch
return false; return false;
} }
private static bool Stop()
{
_burstLeft = 0;
return true;
}
private static void Fire(RangedWeaponController controller, RangedWeapon.Ammo ammo) private static void Fire(RangedWeaponController controller, RangedWeapon.Ammo ammo)
{ {
var before = ammo.GetRemainingAmmo(); 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}"); RLog.Msg($"FullAuto first shot with {name}: ammo {before} -> {after}, audio {controller._playFireAudio}");
if (FullAuto.Verbose) 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) private static bool IsAllowed(RangedWeaponController controller)
@@ -235,6 +335,4 @@ internal static class CheckFireInputPatch
RLog.Msg($"FullAuto {name}: {(allowed ? "full-auto" : "ignored")}"); RLog.Msg($"FullAuto {name}: {(allowed ? "full-auto" : "ignored")}");
return allowed; return allowed;
} }
} }
+23 -18
View File
@@ -1,8 +1,8 @@
# FullAuto # 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. adjustable and your settings are saved between sessions.
## Multiplayer ## Multiplayer
@@ -43,30 +43,36 @@ Mods\FullAuto\manifest.json
Equip a gun and hold the fire button. Equip a gun and hold the fire button.
| Control | Action | With a gun equipped, click the middle mouse button to cycle fire modes:
| --- | --- |
| F8 | Toggle the mod on or off |
| F1 | Open the console |
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 | | Command | Action |
| --- | --- | | --- | --- |
| `fullauto` | Show current settings | | `fullauto` | Show current settings |
| `fullauto on` | Enable | | `fullauto on` | Enable |
| `fullauto off` | Disable | | `fullauto off` | Disable until the game restarts |
| `fullauto rpm 1200` | Set the fire rate, 60 to 6000 rounds per minute | | `fullauto toggle` | Switch between on and off |
| `fullauto mode shoot` | Change the fire method, `shoot`, `fire` or `trigger` | | `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 The default is 900 rpm. The rpm also sets how fast burst shots fire.
another mode.
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 Only the pistol, revolver, shotgun and rifle are affected. Bows, the crossbow,
bombs and rocks keep their normal behavior. slingshot, flare gun, stun gun, rope gun and thrown items keep their normal
behavior.
## Building from source ## Building from source
@@ -82,5 +88,4 @@ the game is not found automatically.
## Troubleshooting ## 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