14 Commits
3 changed files with 216 additions and 19 deletions
+189 -14
View File
@@ -28,9 +28,18 @@ 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 readonly Dictionary<string, FireMode> Modes = new();
internal static string CurrentGun = "";
internal static FireMode Mode
{
get => Modes.TryGetValue(CurrentGun, out var mode) ? mode : FireMode.Auto;
set => Modes[CurrentGun] = value;
}
internal static bool FastReload; internal static bool FastReload;
internal static bool AutoReload;
internal static KeyCode FlashlightKey = KeyCode.F; internal static KeyCode FlashlightKey = KeyCode.F;
internal static string ReloadKey = "r";
internal static bool Verbose; internal static bool Verbose;
private static string _configPath; private static string _configPath;
@@ -46,12 +55,13 @@ public class FullAuto : SonsMod
_configPath = Path.Combine(LoaderEnvironment.UserDataDirectory, "FullAuto.txt"); _configPath = Path.Combine(LoaderEnvironment.UserDataDirectory, "FullAuto.txt");
Load(); Load();
Save(); Save();
RLog.Msg($"FullAuto loaded. RPM: {Rpm}. Fast reload: {FastReload}. Flashlight key: {FlashlightKey}. Guns only. Middle mouse cycles modes. Console: fullauto"); RLog.Msg($"FullAuto loaded. RPM: {Rpm}. Fast reload: {FastReload}. Auto reload: {AutoReload}. Flashlight key: {FlashlightKey}. Guns only. Middle mouse cycles modes. Console: fullauto");
} }
private void OnUpdate() private void OnUpdate()
{ {
CheckFireInputPatch.UpdateTrigger(); CheckFireInputPatch.UpdateTrigger();
CheckFireInputPatch.UpdateReloadKey();
if (!CheckFireInputPatch.HoldingGun) if (!CheckFireInputPatch.HoldingGun)
CheckFireInputPatch.RestoreReloadSpeed(); CheckFireInputPatch.RestoreReloadSpeed();
@@ -71,8 +81,9 @@ public class FullAuto : SonsMod
_ => FireMode.Auto _ => FireMode.Auto
}; };
CheckFireInputPatch.CancelBurst(); CheckFireInputPatch.CancelBurst();
Save();
ShowMode(); ShowMode();
RLog.Msg($"FullAuto mode: {ModeName()}"); RLog.Msg($"FullAuto {CurrentGun} mode: {ModeName()}");
} }
} }
@@ -114,6 +125,18 @@ public class FullAuto : SonsMod
} }
Usage(); Usage();
return; return;
case "autoreload":
if (parts.Length > 1 && (parts[1].ToLowerInvariant() == "on" || parts[1].ToLowerInvariant() == "off"))
{
AutoReload = parts[1].ToLowerInvariant() == "on";
Save();
var autoText = $"Auto reload {(AutoReload ? "ON" : "OFF")}";
SonsTools.ShowMessage(autoText);
RLog.Msg(autoText);
return;
}
Usage();
return;
case "rpm": case "rpm":
if (parts.Length > 1 && float.TryParse(parts[1], out var value)) if (parts.Length > 1 && float.TryParse(parts[1], out var value))
{ {
@@ -134,7 +157,7 @@ public class FullAuto : SonsMod
private static void Usage() private static void Usage()
{ {
var text = $"Usage: fullauto [on|off|debug], fullauto rpm <{MinRpm}-{MaxRpm}>, fullauto fastreload on|off"; var text = $"Usage: fullauto [on|off|debug], fullauto rpm <{MinRpm}-{MaxRpm}>, fullauto fastreload on|off, fullauto autoreload on|off";
SonsTools.ShowMessage(text, 5f); SonsTools.ShowMessage(text, 5f);
RLog.Msg(text); RLog.Msg(text);
} }
@@ -156,7 +179,7 @@ public class FullAuto : SonsMod
internal static void Announce() internal static void Announce()
{ {
var reload = FastReload ? ", fast reload on" : ""; var reload = (FastReload ? ", fast reload on" : "") + (AutoReload ? ", auto reload on" : "");
var text = Enabled ? $"{ModeName()} ({Rpm} RPM{reload})" : $"FullAuto OFF{reload}"; var text = Enabled ? $"{ModeName()} ({Rpm} RPM{reload})" : $"FullAuto OFF{reload}";
SonsTools.ShowMessage(text); SonsTools.ShowMessage(text);
RLog.Msg(text); RLog.Msg(text);
@@ -182,8 +205,14 @@ public class FullAuto : SonsMod
Rpm = Mathf.Clamp(rpm, MinRpm, MaxRpm); Rpm = Mathf.Clamp(rpm, MinRpm, MaxRpm);
else if (key == "fastreload" && bool.TryParse(val, out var fast)) else if (key == "fastreload" && bool.TryParse(val, out var fast))
FastReload = fast; FastReload = fast;
else if (key == "autoreload" && bool.TryParse(val, out var auto))
AutoReload = auto;
else if (key == "reloadkey" && val.Length > 0)
ReloadKey = val.ToLowerInvariant();
else if (key == "flashlightkey" && Enum.TryParse<KeyCode>(val, true, out var flashKey)) else if (key == "flashlightkey" && Enum.TryParse<KeyCode>(val, true, out var flashKey))
FlashlightKey = flashKey; FlashlightKey = flashKey;
else if (key.StartsWith("mode.") && Enum.TryParse<FireMode>(val, true, out var gunMode))
Modes[key.Substring(5)] = gunMode;
} }
} }
catch (Exception e) catch (Exception e)
@@ -196,7 +225,18 @@ public class FullAuto : SonsMod
{ {
try try
{ {
File.WriteAllText(_configPath, $"rpm={Rpm}\nfastreload={FastReload.ToString().ToLowerInvariant()}\nflashlightkey={FlashlightKey}\n"); var sb = new System.Text.StringBuilder();
sb.Append($"rpm={Rpm}\n");
sb.Append($"fastreload={FastReload.ToString().ToLowerInvariant()}\n");
sb.Append($"autoreload={AutoReload.ToString().ToLowerInvariant()}\n");
sb.Append($"flashlightkey={FlashlightKey}\n");
sb.Append($"reloadkey={ReloadKey}\n");
foreach (var gun in CheckFireInputPatch.GunKeys)
{
var mode = Modes.TryGetValue(gun, out var m) ? m : FireMode.Auto;
sb.Append($"mode.{gun}={mode.ToString().ToLowerInvariant()}\n");
}
File.WriteAllText(_configPath, sb.ToString());
} }
catch (Exception e) catch (Exception e)
{ {
@@ -211,15 +251,20 @@ internal static class CheckFireInputPatch
private const int MaxShotsPerFrame = 4; private const int MaxShotsPerFrame = 4;
private const int BurstSize = 3; private const int BurstSize = 3;
private const float ReleaseTime = 0.15f; private const float ReleaseTime = 0.15f;
private const float AutoReloadDelay = 0.2f;
private static readonly HashSet<string> Guns = new() private static readonly Dictionary<string, string> Guns = new()
{ {
"CompactPistolWeaponController", { "CompactPistolWeaponController", "pistol" },
"RevolverWeaponController", { "RevolverWeaponController", "revolver" },
"ShotgunWeaponController", { "ShotgunWeaponController", "shotgun" },
"RifleAnimatorController" { "RifleAnimatorController", "rifle" }
}; };
internal static IEnumerable<string> GunKeys => Guns.Values;
private static readonly Dictionary<IntPtr, string> GunNames = new();
private static readonly Dictionary<IntPtr, bool> Allowed = new(); private static readonly Dictionary<IntPtr, bool> Allowed = new();
private static readonly Dictionary<IntPtr, float> FireDelays = new(); private static readonly Dictionary<IntPtr, float> FireDelays = new();
private static readonly HashSet<string> Reported = new(); private static readonly HashSet<string> Reported = new();
@@ -241,6 +286,13 @@ internal static class CheckFireInputPatch
private static bool _flashlightOff; private static bool _flashlightOff;
private static IntPtr _lightsOwner = IntPtr.Zero; private static IntPtr _lightsOwner = IntPtr.Zero;
private static readonly List<Light> Lights = new(); private static readonly List<Light> Lights = new();
private static float _emptySince = -1f;
private static float _nextReloadTry;
private static UnityEngine.InputSystem.Controls.KeyControl _reloadControl;
private static string _reloadControlKey;
private static UnityEngine.InputSystem.Controls.KeyControl _pressedKey;
private static int _releaseFrame = -1;
private static bool _keySimFailed;
private static bool _loggedHook; private static bool _loggedHook;
internal static bool HoldingGun => _lastGunTime >= 0f && Time.time - _lastGunTime < 0.25f; internal static bool HoldingGun => _lastGunTime >= 0f && Time.time - _lastGunTime < 0.25f;
@@ -303,6 +355,114 @@ internal static class CheckFireInputPatch
} }
} }
private static void TryAutoReload(RangedWeaponController controller)
{
if (!FullAuto.AutoReload || !InGame)
{
_emptySince = -1f;
return;
}
if (controller._isReloading || controller.IsReloading() || controller.IsReloadQueued())
{
_emptySince = -1f;
return;
}
var weapon = controller.GetRangedWeapon();
if (!weapon)
return;
var ammo = weapon.GetAmmo();
if (ammo == null || !ammo.IsEmpty())
{
_emptySince = -1f;
return;
}
var now = Time.time;
if (_emptySince < 0f)
{
_emptySince = now;
return;
}
if (now - _emptySince < AutoReloadDelay || now < _nextReloadTry)
return;
_nextReloadTry = now + 0.5f;
if (!controller.CanReload())
{
if (FullAuto.Verbose)
RLog.Msg($"FullAuto auto reload waiting, game says cannot reload (aiming: {controller.IsAiming})");
return;
}
if (PressReloadKey())
{
if (FullAuto.Verbose)
RLog.Msg($"FullAuto auto reload pressed {FullAuto.ReloadKey} on {controller.GetIl2CppType().Name}");
return;
}
controller._reloadQueued = true;
if (FullAuto.Verbose)
RLog.Msg($"FullAuto auto reload queued on {controller.GetIl2CppType().Name}");
}
private static bool PressReloadKey()
{
if (_keySimFailed || _pressedKey != null)
return false;
try
{
if (_reloadControl == null || _reloadControlKey != FullAuto.ReloadKey)
{
_reloadControlKey = FullAuto.ReloadKey;
var control = UnityEngine.InputSystem.InputSystem.FindControl($"<Keyboard>/{FullAuto.ReloadKey}");
_reloadControl = control?.TryCast<UnityEngine.InputSystem.Controls.KeyControl>();
}
if (_reloadControl == null)
{
_keySimFailed = true;
RLog.Warning($"FullAuto could not find reload key {FullAuto.ReloadKey}");
return false;
}
UnityEngine.InputSystem.InputControlExtensions.QueueValueChange<float>(_reloadControl, 1f, -1d);
_pressedKey = _reloadControl;
_releaseFrame = Time.frameCount + 3;
return true;
}
catch (Exception e)
{
_keySimFailed = true;
RLog.Warning($"FullAuto could not press reload key: {e.Message}");
return false;
}
}
internal static void UpdateReloadKey()
{
if (_pressedKey == null || Time.frameCount < _releaseFrame)
return;
try
{
UnityEngine.InputSystem.InputControlExtensions.QueueValueChange<float>(_pressedKey, 0f, -1d);
}
catch (Exception e)
{
RLog.Warning($"FullAuto could not release reload key: {e.Message}");
}
_pressedKey = null;
}
internal static void CancelBurst() internal static void CancelBurst()
{ {
_burstLeft = 0; _burstLeft = 0;
@@ -511,8 +671,20 @@ internal static class CheckFireInputPatch
UpdateReloadSpeed(__instance, allowed); UpdateReloadSpeed(__instance, allowed);
if (allowed && FullAuto.Enabled && (!HoldingGun || ptr != _lastGunPtr)) if (allowed)
FullAuto.ShowMode(); TryAutoReload(__instance);
if (allowed && GunNames.TryGetValue(ptr, out var gunName))
{
var switched = !HoldingGun || ptr != _lastGunPtr;
FullAuto.CurrentGun = gunName;
if (switched)
{
CancelBurst();
if (FullAuto.Enabled)
FullAuto.ShowMode();
}
}
if (allowed) if (allowed)
_lastGunPtr = ptr; _lastGunPtr = ptr;
@@ -628,10 +800,13 @@ internal static class CheckFireInputPatch
return allowed; return allowed;
var name = controller.GetIl2CppType().Name; var name = controller.GetIl2CppType().Name;
allowed = Guns.Contains(name); allowed = Guns.TryGetValue(name, out var gunKey);
Allowed[ptr] = allowed; Allowed[ptr] = allowed;
if (allowed) if (allowed)
{
FireDelays[ptr] = controller._fireDelay; FireDelays[ptr] = controller._fireDelay;
GunNames[ptr] = gunKey;
}
RLog.Msg($"FullAuto {name}: {(allowed ? "full-auto" : "ignored")}"); RLog.Msg($"FullAuto {name}: {(allowed ? "full-auto" : "ignored")}");
return allowed; return allowed;
} }
+25 -3
View File
@@ -1,5 +1,7 @@
# FullAuto # FullAuto
[![Downloads](https://img.shields.io/github/downloads/JustinOros/sotf-fullauto/total?label=downloads)](https://github.com/JustinOros/sotf-fullauto/releases) [![Latest](https://img.shields.io/github/v/release/JustinOros/sotf-fullauto?label=latest)](https://github.com/JustinOros/sotf-fullauto/releases/latest)
Hold the fire button to shoot guns 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 and rifle. The fire rate is Works with the pistol, revolver, shotgun and rifle. The fire rate is
@@ -64,7 +66,11 @@ With a gun equipped, click the middle mouse button to cycle fire modes:
| Semi-automatic | Normal game behavior, one shot per click | | Semi-automatic | Normal game behavior, one shot per click |
| 3-Round Burst | Fires 3 shots, then release and press again for the next 3 | | 3-Round Burst | Fires 3 shots, then release and press again for the next 3 |
The mod starts in full-automatic every time the game loads. Each gun remembers its own fire mode, even after you restart the game. Guns
start in full-automatic until you change them.
Press F to turn a flashlight attached to your gun or crossbow on or off.
The key can be changed with the `flashlightkey=` line in `UserData\FullAuto.txt`.
Press F1 to open the console, then use these commands: Press F1 to open the console, then use these commands:
@@ -73,13 +79,16 @@ Press F1 to open the console, then use these commands:
| `fullauto` | Show current settings | | `fullauto` | Show current settings |
| `fullauto on` | Enable | | `fullauto on` | Enable |
| `fullauto off` | Disable until the game restarts | | `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 rpm 1200` | Set the fire rate, 60 to 3000 rounds per minute |
| `fullauto fastreload on` | Reload guns twice as fast, `off` to turn it back off |
| `fullauto autoreload on` | Reload automatically when the gun is empty, `off` to turn it back off. It presses your reload key for you, so if you changed it from R in the game settings, set `reloadkey=` in `FullAuto.txt` to match |
| `fullauto debug` | Log every shot to `_RedLoader\Latest.log` | | `fullauto debug` | Log every shot to `_RedLoader\Latest.log` |
The default is 900 rpm. The rpm also sets how fast burst shots fire. The default is 900 rpm. The rpm also sets how fast burst shots fire.
The rpm is saved to `UserData\FullAuto.txt` in your game folder. The rpm, fast reload, auto reload, flashlight key, reload key and each gun's fire
mode are saved to
`UserData\FullAuto.txt` in your game folder.
## Supported weapons ## Supported weapons
@@ -89,4 +98,17 @@ behavior.
## Building from source ## Building from source
Requires the .NET 8 SDK and RedLoader installed with its game assemblies
generated.
```powershell
.\build.ps1 -Install
```
Use `-Package` to build `FullAuto.zip` for a release. Pass `-GameDir "path"` if
the game is not found automatically.
## Troubleshooting
Check `_RedLoader\Latest.log` in your game folder. FullAuto logs a line when it loads and a line for each weapon showing whether it is full-auto or
ignored.
+2 -2
View File
@@ -3,8 +3,8 @@
"id": "FullAuto", "id": "FullAuto",
"name": "FullAuto", "name": "FullAuto",
"author": "JustinOros", "author": "JustinOros",
"version": "1.3.0", "version": "1.5.5",
"description": "Hold fire to shoot any gun full-auto at a configurable rate", "description": "Full-auto, semi-auto and 3-round burst for guns, with fast reload and a flashlight toggle",
"gameVersion": "1.0.0", "gameVersion": "1.0.0",
"platform": "Client", "platform": "Client",
"type": "Mod" "type": "Mod"