Initial commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
bin/
|
||||
obj/
|
||||
dist/
|
||||
*.zip
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using HarmonyLib;
|
||||
using RedLoader;
|
||||
using RedLoader.Utils;
|
||||
using Sons.Weapon;
|
||||
using SonsSdk;
|
||||
using SonsSdk.Attributes;
|
||||
using TheForest.Utils;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FullAuto;
|
||||
|
||||
public class FullAuto : SonsMod
|
||||
{
|
||||
internal const float MinRpm = 60f;
|
||||
internal const float MaxRpm = 3000f;
|
||||
|
||||
internal static bool Enabled = true;
|
||||
internal static float Rpm = 900f;
|
||||
internal static bool Verbose;
|
||||
|
||||
private static string _configPath;
|
||||
|
||||
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}. F10 toggles, console: fullauto");
|
||||
}
|
||||
|
||||
private void OnUpdate()
|
||||
{
|
||||
if (!GameState.IsPlayerControllable)
|
||||
return;
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.F10))
|
||||
{
|
||||
Enabled = !Enabled;
|
||||
Save();
|
||||
Announce();
|
||||
}
|
||||
}
|
||||
|
||||
[DebugCommand("fullauto")]
|
||||
private static void FullAutoCommand(string args)
|
||||
{
|
||||
var parts = (args ?? string.Empty).Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
Announce();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (parts[0].ToLowerInvariant())
|
||||
{
|
||||
case "on":
|
||||
Enabled = true;
|
||||
break;
|
||||
case "off":
|
||||
Enabled = false;
|
||||
break;
|
||||
case "toggle":
|
||||
Enabled = !Enabled;
|
||||
break;
|
||||
case "debug":
|
||||
Verbose = !Verbose;
|
||||
SonsTools.ShowMessage($"FullAuto debug: {Verbose}");
|
||||
RLog.Msg($"FullAuto debug: {Verbose}");
|
||||
return;
|
||||
case "rpm":
|
||||
if (parts.Length > 1 && float.TryParse(parts[1], out var value))
|
||||
{
|
||||
Rpm = Mathf.Clamp(value, MinRpm, MaxRpm);
|
||||
break;
|
||||
}
|
||||
Usage();
|
||||
return;
|
||||
default:
|
||||
Usage();
|
||||
return;
|
||||
}
|
||||
|
||||
Save();
|
||||
Announce();
|
||||
}
|
||||
|
||||
private static void Usage()
|
||||
{
|
||||
var text = $"Usage: fullauto [on|off|toggle|debug] or fullauto rpm <{MinRpm}-{MaxRpm}>";
|
||||
SonsTools.ShowMessage(text, 5f);
|
||||
RLog.Msg(text);
|
||||
}
|
||||
|
||||
internal static void Announce()
|
||||
{
|
||||
var text = $"FullAuto {(Enabled ? "ON" : "OFF")} ({Rpm} RPM)";
|
||||
SonsTools.ShowMessage(text);
|
||||
RLog.Msg(text);
|
||||
}
|
||||
|
||||
private static void Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_configPath))
|
||||
return;
|
||||
|
||||
foreach (var line in File.ReadAllLines(_configPath))
|
||||
{
|
||||
var kv = line.Split('=', 2);
|
||||
if (kv.Length != 2)
|
||||
continue;
|
||||
|
||||
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))
|
||||
Rpm = Mathf.Clamp(rpm, MinRpm, MaxRpm);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
RLog.Error($"FullAuto could not read config: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllText(_configPath, $"enabled={Enabled}\nrpm={Rpm}\n");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
RLog.Error($"FullAuto could not write config: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(RangedWeaponController), nameof(RangedWeaponController.CheckFireInput))]
|
||||
internal static class CheckFireInputPatch
|
||||
{
|
||||
private const int MaxShotsPerFrame = 4;
|
||||
|
||||
private static readonly HashSet<string> Excluded = new()
|
||||
{
|
||||
"GrenadeWeaponController",
|
||||
"MolotovWeaponController",
|
||||
"TimeBombWeaponController",
|
||||
"SmallRockWeaponController",
|
||||
"RopeGunController"
|
||||
};
|
||||
|
||||
private static readonly Dictionary<IntPtr, bool> Allowed = new();
|
||||
private static readonly HashSet<string> Reported = new();
|
||||
private static float _nextShot;
|
||||
private static bool _loggedHook;
|
||||
|
||||
private static bool Prefix(RangedWeaponController __instance)
|
||||
{
|
||||
if (!_loggedHook)
|
||||
{
|
||||
_loggedHook = true;
|
||||
RLog.Msg("FullAuto: CheckFireInput hook is firing");
|
||||
}
|
||||
|
||||
if (!FullAuto.Enabled || !__instance.IsLocalPlayer() || !IsAllowed(__instance))
|
||||
return true;
|
||||
|
||||
if (!Input.GetMouseButton(0) || !GameState.IsPlayerControllable)
|
||||
return true;
|
||||
|
||||
if (__instance._isReloading || __instance.IsReloading())
|
||||
return true;
|
||||
|
||||
if (__instance._mustAimToFire && !__instance.IsAiming)
|
||||
return true;
|
||||
|
||||
var weapon = __instance.GetRangedWeapon();
|
||||
if (!weapon)
|
||||
return true;
|
||||
|
||||
var ammo = weapon.GetAmmo();
|
||||
if (ammo == null || ammo.GetRemainingAmmo() <= 0)
|
||||
return true;
|
||||
|
||||
var now = Time.time;
|
||||
var interval = 60f / FullAuto.Rpm;
|
||||
|
||||
if (_nextShot < now - interval)
|
||||
_nextShot = now;
|
||||
|
||||
var shots = 0;
|
||||
while (_nextShot <= now && shots < MaxShotsPerFrame && ammo.GetRemainingAmmo() > 0)
|
||||
{
|
||||
Fire(__instance, ammo);
|
||||
_nextShot += interval;
|
||||
shots++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void Fire(RangedWeaponController controller, RangedWeapon.Ammo ammo)
|
||||
{
|
||||
var before = ammo.GetRemainingAmmo();
|
||||
|
||||
controller._fireDelay = 0f;
|
||||
controller._nextFireDelay = 0f;
|
||||
controller.FireWeapon();
|
||||
|
||||
if (controller._playFireAudio == RangedWeaponController.FireAudio.OnAnimShootCallback)
|
||||
{
|
||||
controller.TriggerShotFiredAudio();
|
||||
if (controller.ShouldShowMuzzleFlash())
|
||||
controller.OnMuzzleFlash();
|
||||
}
|
||||
|
||||
controller._attackState = RangedWeaponController.AttackState.Idle;
|
||||
|
||||
var after = ammo.GetRemainingAmmo();
|
||||
var name = controller.GetIl2CppType().Name;
|
||||
|
||||
if (Reported.Add(name))
|
||||
RLog.Msg($"FullAuto first shot with {name}: ammo {before} -> {after}, audio {controller._playFireAudio}");
|
||||
|
||||
if (FullAuto.Verbose)
|
||||
RLog.Msg($"FullAuto shot {name}: ammo {before} -> {after}");
|
||||
}
|
||||
|
||||
private static bool IsAllowed(RangedWeaponController controller)
|
||||
{
|
||||
var ptr = controller.Pointer;
|
||||
if (Allowed.TryGetValue(ptr, out var allowed))
|
||||
return allowed;
|
||||
|
||||
allowed = !Excluded.Contains(controller.GetIl2CppType().Name);
|
||||
Allowed[ptr] = allowed;
|
||||
return allowed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AssemblyName>FullAuto</AssemblyName>
|
||||
<RootNamespace>FullAuto</RootNamespace>
|
||||
<Version>1.0.0</Version>
|
||||
<NoWarn>CS0436;CS1701;CS1702;MSB3277;MSB3106</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GameDir)' == ''">
|
||||
<GameDir>$(SOTF_PATH)</GameDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="CheckGameDir" BeforeTargets="BeforeBuild">
|
||||
<Error Condition="'$(GameDir)' == ''"
|
||||
Text="GameDir is not set. Run build.ps1, pass -p:GameDir="path", or set SOTF_PATH." />
|
||||
<Error Condition="!Exists('$(GameDir)\_RedLoader\Game\Sons.dll')"
|
||||
Text="No RedLoader interop assemblies at $(GameDir)\_RedLoader\Game. Install RedLoader and launch the game once." />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="$(GameDir)\_RedLoader\Game\*.dll" Private="false" />
|
||||
<Reference Include="$(GameDir)\_RedLoader\net6\RedLoader.dll" Private="false" />
|
||||
<Reference Include="$(GameDir)\_RedLoader\net6\SonsSdk.dll" Private="false" />
|
||||
<Reference Include="$(GameDir)\_RedLoader\net6\Il2CppInterop.Runtime.dll" Private="false" />
|
||||
<Reference Include="$(GameDir)\_RedLoader\net6\Il2CppInterop.Common.dll" Private="false" />
|
||||
<Reference Include="$(GameDir)\_RedLoader\net6\0Harmony.dll" Private="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="manifest.json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,86 @@
|
||||
# FullAuto
|
||||
|
||||
Hold the fire button to shoot any gun full-auto in Sons of the Forest.
|
||||
|
||||
Works with the pistol, revolver, shotgun, rifle and other guns. The fire rate is
|
||||
adjustable and your settings are saved between sessions.
|
||||
|
||||
## Multiplayer
|
||||
|
||||
Client side only. Nothing gets installed on a dedicated server, and other
|
||||
players do not need the mod.
|
||||
|
||||
## Installation
|
||||
|
||||
### Step 1: Install RedLoader
|
||||
|
||||
[RedLoader](https://github.com/ToniMacaroni/RedLoader/releases/latest) is the mod
|
||||
loader for Sons of the Forest. The game cannot load any mod without it, so
|
||||
install it first. You only have to do this once.
|
||||
|
||||
1. Download `RedLoader.zip` from the
|
||||
[latest RedLoader release](https://github.com/ToniMacaroni/RedLoader/releases/latest)
|
||||
2. Extract it into your Sons of the Forest folder, the one containing
|
||||
`SonsOfTheForest.exe`, usually
|
||||
`C:\Program Files (x86)\Steam\steamapps\common\Sons Of The Forest`
|
||||
3. Launch the game once and wait until you reach the main menu. The first launch
|
||||
takes a few minutes while RedLoader processes the game files
|
||||
4. Check that `MODS` appears on the main menu, then quit
|
||||
|
||||
### Step 2: Install FullAuto
|
||||
|
||||
1. Download `FullAuto.zip` from the
|
||||
[latest release](https://github.com/JustinOros/sotf-fullauto/releases/latest)
|
||||
2. Extract it into the `Mods` folder inside your game folder
|
||||
3. You should end up with:
|
||||
|
||||
```
|
||||
Mods\FullAuto.dll
|
||||
Mods\FullAuto\manifest.json
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Equip a gun and hold the fire button.
|
||||
|
||||
| Control | Action |
|
||||
| --- | --- |
|
||||
| F8 | Toggle the mod on or off |
|
||||
| F1 | Open the console |
|
||||
|
||||
Console 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` |
|
||||
|
||||
The default is 900 rpm with mode `shoot`. If a gun does not fire full-auto, try
|
||||
another mode.
|
||||
|
||||
Settings are saved to `UserData\FullAuto.cfg` in your game folder.
|
||||
|
||||
## Excluded weapons
|
||||
|
||||
The bow, slingshot, rope gun and thrown items such as grenades, molotovs, time
|
||||
bombs and rocks keep their normal behavior.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$GameDir,
|
||||
[switch]$Install,
|
||||
[switch]$Package
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Test-Dotnet {
|
||||
$cmd = Get-Command dotnet -ErrorAction SilentlyContinue
|
||||
if (-not $cmd) { return $false }
|
||||
$sdks = & dotnet --list-sdks 2>$null
|
||||
return ($LASTEXITCODE -eq 0 -and $sdks)
|
||||
}
|
||||
|
||||
function Update-Path {
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' +
|
||||
[System.Environment]::GetEnvironmentVariable('Path','User')
|
||||
$dotnetRoot = Join-Path $env:ProgramFiles 'dotnet'
|
||||
if ((Test-Path $dotnetRoot) -and ($env:Path -notlike "*$dotnetRoot*")) {
|
||||
$env:Path += ";$dotnetRoot"
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Dotnet {
|
||||
if (Get-Command winget -ErrorAction SilentlyContinue) {
|
||||
Write-Host 'Installing the .NET 8 SDK with winget' -ForegroundColor Cyan
|
||||
winget install --id Microsoft.DotNet.SDK.8 --accept-source-agreements --accept-package-agreements --silent
|
||||
Update-Path
|
||||
if (Test-Dotnet) { return }
|
||||
Write-Host ' winget install did not make dotnet available, falling back' -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host 'Downloading the official dotnet-install script' -ForegroundColor Cyan
|
||||
$script = Join-Path $env:TEMP 'dotnet-install.ps1'
|
||||
Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile $script -UseBasicParsing
|
||||
|
||||
Write-Host 'Installing the .NET 8 SDK to your user profile' -ForegroundColor Cyan
|
||||
& $script -Channel 8.0 -InstallDir "$env:LOCALAPPDATA\Microsoft\dotnet"
|
||||
$userDotnet = "$env:LOCALAPPDATA\Microsoft\dotnet"
|
||||
$env:Path = "$userDotnet;$env:Path"
|
||||
[Environment]::SetEnvironmentVariable(
|
||||
'Path',
|
||||
"$userDotnet;" + [Environment]::GetEnvironmentVariable('Path','User'),
|
||||
'User')
|
||||
Remove-Item $script -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
function Install-RedLoader {
|
||||
param([string]$Target)
|
||||
|
||||
Write-Host 'Looking up the latest RedLoader release' -ForegroundColor Cyan
|
||||
$release = Invoke-RestMethod 'https://api.github.com/repos/ToniMacaroni/RedLoader/releases/latest' -Headers @{ 'User-Agent' = 'ps' }
|
||||
$asset = $release.assets | Where-Object { $_.name -match '^RedLoader.*\.zip$' } | Select-Object -First 1
|
||||
if (-not $asset) { throw 'Could not find RedLoader.zip in the latest release' }
|
||||
Write-Host " $($release.tag_name) / $($asset.name)"
|
||||
|
||||
$zip = Join-Path $env:TEMP $asset.name
|
||||
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $zip -UseBasicParsing
|
||||
|
||||
$stage = Join-Path $env:TEMP 'redloader-extract'
|
||||
if (Test-Path $stage) { Remove-Item $stage -Recurse -Force }
|
||||
Expand-Archive -Path $zip -DestinationPath $stage -Force
|
||||
Copy-Item -Path (Join-Path $stage '*') -Destination $Target -Recurse -Force
|
||||
Remove-Item $zip -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item $stage -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Host ' RedLoader installed' -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Find-GameDir {
|
||||
if ($env:SOTF_PATH) { return $env:SOTF_PATH }
|
||||
$steam = (Get-ItemProperty 'HKCU:\Software\Valve\Steam' -ErrorAction SilentlyContinue).SteamPath
|
||||
if (-not $steam) { return $null }
|
||||
$libs = @($steam)
|
||||
$vdf = Join-Path $steam 'steamapps\libraryfolders.vdf'
|
||||
if (Test-Path $vdf) {
|
||||
Select-String -Path $vdf -Pattern '"path"\s+"(.+?)"' -AllMatches |
|
||||
ForEach-Object { $_.Matches } |
|
||||
ForEach-Object { $libs += $_.Groups[1].Value.Replace('\\','\') }
|
||||
}
|
||||
foreach ($lib in $libs) {
|
||||
$candidate = Join-Path $lib 'steamapps\common\Sons Of The Forest'
|
||||
if (Test-Path (Join-Path $candidate 'SonsOfTheForest.exe')) { return $candidate }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
if (-not (Test-Dotnet)) {
|
||||
Write-Host ''
|
||||
Write-Host 'The .NET 8 SDK is required to build this mod and was not found.' -ForegroundColor Yellow
|
||||
$answer = Read-Host 'Download and install it now? (y/N)'
|
||||
if ($answer -ne 'y') {
|
||||
throw 'Cannot build without the .NET SDK. Get it at https://dotnet.microsoft.com/download'
|
||||
}
|
||||
Install-Dotnet
|
||||
if (-not (Test-Dotnet)) {
|
||||
throw 'The SDK was installed but dotnet is still not on PATH. Reopen PowerShell and rerun this script.'
|
||||
}
|
||||
}
|
||||
Write-Host ("dotnet SDK: " + (dotnet --version)) -ForegroundColor Green
|
||||
|
||||
if (-not $GameDir) { $GameDir = Find-GameDir }
|
||||
if (-not $GameDir) {
|
||||
throw 'Could not find Sons Of The Forest. Pass -GameDir "path" or set SOTF_PATH.'
|
||||
}
|
||||
Write-Host "Game directory: $GameDir" -ForegroundColor Cyan
|
||||
|
||||
if (-not (Test-Path (Join-Path $GameDir '_RedLoader\net6\SonsSdk.dll'))) {
|
||||
Write-Host ''
|
||||
Write-Host 'RedLoader is not installed in your game folder.' -ForegroundColor Yellow
|
||||
$answer = Read-Host 'Download and install the latest RedLoader release now? (y/N)'
|
||||
if ($answer -ne 'y') {
|
||||
throw 'RedLoader is required. Get it at https://github.com/ToniMacaroni/RedLoader/releases'
|
||||
}
|
||||
Install-RedLoader -Target $GameDir
|
||||
}
|
||||
|
||||
if (-not (Test-Path (Join-Path $GameDir '_RedLoader\Game\Sons.dll'))) {
|
||||
Write-Host ''
|
||||
Write-Host 'RedLoader is installed but has not generated its game assemblies yet.' -ForegroundColor Yellow
|
||||
Write-Host 'Launch Sons of the Forest once and wait for the main menu. The first'
|
||||
Write-Host 'launch takes a few minutes while it processes the game files.'
|
||||
Write-Host ''
|
||||
Write-Host 'Then quit the game and run this script again.'
|
||||
return
|
||||
}
|
||||
Write-Host 'RedLoader ready' -ForegroundColor Green
|
||||
|
||||
dotnet build -c Release --nologo -p:GameDir="$GameDir"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Build failed' }
|
||||
|
||||
$dll = Join-Path $PSScriptRoot 'bin\Release\net6\FullAuto.dll'
|
||||
|
||||
if ($Install) {
|
||||
$modsDir = Join-Path $GameDir 'Mods'
|
||||
$modDataDir = Join-Path $modsDir 'FullAuto'
|
||||
New-Item -ItemType Directory -Force -Path $modDataDir | Out-Null
|
||||
Copy-Item $dll -Destination $modsDir -Force
|
||||
Copy-Item (Join-Path $PSScriptRoot 'manifest.json') -Destination $modDataDir -Force
|
||||
Write-Host "Installed to $modsDir" -ForegroundColor Green
|
||||
}
|
||||
|
||||
if ($Package) {
|
||||
$stage = Join-Path $PSScriptRoot 'dist'
|
||||
$zip = Join-Path $PSScriptRoot 'FullAuto.zip'
|
||||
|
||||
if (Test-Path $stage) { Remove-Item $stage -Recurse -Force }
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $stage 'FullAuto') | Out-Null
|
||||
Copy-Item $dll -Destination $stage -Force
|
||||
Copy-Item (Join-Path $PSScriptRoot 'manifest.json') -Destination (Join-Path $stage 'FullAuto') -Force
|
||||
|
||||
if (Test-Path $zip) { Remove-Item $zip -Force }
|
||||
Compress-Archive -Path (Join-Path $stage '*') -DestinationPath $zip
|
||||
Remove-Item $stage -Recurse -Force
|
||||
|
||||
Write-Host "Packaged $zip" -ForegroundColor Green
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/ToniMacaroni/RedLoader/main/MetadataSchema.json",
|
||||
"id": "FullAuto",
|
||||
"name": "FullAuto",
|
||||
"author": "JustinOros",
|
||||
"version": "1.0.0",
|
||||
"description": "Hold fire to shoot any gun full-auto at a configurable rate",
|
||||
"gameVersion": "1.0.0",
|
||||
"platform": "Client",
|
||||
"type": "Mod"
|
||||
}
|
||||
Reference in New Issue
Block a user