From ad9987cac02bde44348757acea6fe7442ea93b0c Mon Sep 17 00:00:00 2001 From: Justin Oros Date: Mon, 28 Sep 2026 11:56:34 -0700 Subject: [PATCH] v1.2.0 move suppressor to sotf-riflesuppressor --- README.md | 21 +-- RifleScope.cs | 319 +--------------------------------------------- RifleScope.csproj | 2 +- manifest.json | 2 +- 4 files changed, 10 insertions(+), 334 deletions(-) diff --git a/README.md b/README.md index 07dfa1d..d14252c 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,11 @@ A stronger rifle scope for Sons of the Forest. Aiming down the rifle scope zooms 5x further than the vanilla scope and shows a full screen scope view with a -crosshair reticle, mil dots and a rangefinder instead of the red dot. You can -also put the pistol suppressor on the rifle for quiet, suppressed shots. +crosshair reticle, mil dots and a rangefinder instead of the red dot. + +Want a suppressed rifle too? Install +[RifleSuppressor](https://github.com/JustinOros/sotf-riflesuppressor). It used to +be part of this mod and is now its own mod, so you can use either one or both. ## Multiplayer @@ -69,7 +72,7 @@ Press F1 to open the console, then use these commands: | Command | Action | | --- | --- | | `scope` | Show whether the scope is on or off | -| `scope off` | Go back to the vanilla scope. The suppressor is not affected | +| `scope off` | Go back to the vanilla scope | | `scope on` | Turn the scope back on | | `scopezoom` | Show the current zoom | | `scopezoom 5` | Set the zoom as a multiple of the vanilla scope, 1 to 50. The default is 5 | @@ -77,22 +80,12 @@ Press F1 to open the console, then use these commands: | `scopetrim 60` | Set the vertical trim, -200 to 200. Positive moves hits down, negative moves hits up. The default is 60 | | `scopewind` | Show the current windage | | `scopewind 20` | Set the windage, -200 to 200. Positive moves hits right, negative moves hits left. The default is 0 | -| `suppressor` | Show whether the rifle suppressor is on or off | -| `suppressor on` | Put the suppressor on the rifle | -| `suppressor off` | Take the suppressor off the rifle | | `scopedebug` | Toggle a debug readout above the range | -### Suppressor - -Run `suppressor on` to put the pistol suppressor on the end of the rifle barrel. -Rifle shots then use the suppressed pistol shot sound. It stays on until you run -`suppressor off`, even after restarting the game. You do not need a suppressor in -your inventory. - Trim and windage are angle adjustments, so a setting that is right at one range stays right at every range. -The on or off setting, suppressor, zoom, trim and windage are saved to `UserData\RifleScope.txt` in your game +The on or off setting, zoom, trim and windage are saved to `UserData\RifleScope.txt` in your game folder. Setting the zoom to 1 keeps vanilla zoom and the normal scope view, without the red dot. diff --git a/RifleScope.cs b/RifleScope.cs index f6b7395..455cc6f 100644 --- a/RifleScope.cs +++ b/RifleScope.cs @@ -30,12 +30,6 @@ public class RifleScope : SonsMod private const float MaxTrimCm = 200f; private const float MaxWindCm = 200f; private const float TrimReferenceRange = 50f; - private const string SuppressorModName = "CompactPistolSuppressorMod"; - private const string SuppressedFireEvent = "event:/SotF Events/player sounds/Weapons/PistolTactical/PistolTacticalFire"; - private const string MuzzleName = "RifleScopeMuzzle"; - private const string RifleFireEvent = "event:/SotF Events/player sounds/Weapons/Rifle/rifle_fire"; - private const string UpgradeParameter = "upgrade"; - private const float SuppressedUpgradeValue = 0.2f; private const int MilDotCount = 10; private const float MilDotSize = 0.4f; private const float MinDotPx = 7f; @@ -56,18 +50,11 @@ public class RifleScope : SonsMod private static float _windCm; private static bool _debug; private static bool _enabled = true; - private static bool _suppressor; - private static string _originalFireEvent; - private static HarmonyLib.Harmony _audioHarmony; - private static bool _loggedParamError; - private static bool _loggedPistolParams; - private static bool _reissuing; private static readonly List<(Renderer Renderer, int Slot, Material Original)> RedDotSlots = new(); private static string _configPath; private static RifleAnimatorController _rifle; private static RifleAnimatorController _pendingRifle; - private static ScriptableObject _suppressorMod; private static float _hipFov; private static float _vanillaAimFov = 35f; private static float _targetAimFov; @@ -166,296 +153,12 @@ public class RifleScope : SonsMod { _rifle = rifle; _projectile = FindProjectileTransform(rifle); - _originalFireEvent ??= rifle._gunShotAudioEvent; if (_enabled) { if (!_overlay) CreateOverlay(); HideRedDot(rifle); } - if (_suppressor) - SetSuppressor(rifle, true); - } - - private static WeaponMod FindSuppressorMod() - { - if (_suppressorMod) - return _suppressorMod.TryCast(); - foreach (var mod in Resources.FindObjectsOfTypeAll()) - { - if (mod && mod.name == SuppressorModName) - { - _suppressorMod = mod; - return mod; - } - } - return null; - } - - private static Transform FindChild(Transform root, string name) - { - foreach (var t in root.GetComponentsInChildren(true)) - { - if (t.name == name) - return t; - } - return null; - } - - private static bool EnsureMuzzleSlot(RangedWeaponController rifle, WeaponMods mods) - { - var links = mods._modLocations; - for (var i = 0; i < links.Count; i++) - { - if (links[i].Slot == WeaponMod.Slot.Muzzle) - return true; - } - - var anchor = FindChild(rifle.transform, "ProjectileVisualTransform"); - if (!anchor) - return false; - - var muzzle = FindChild(anchor.parent, MuzzleName); - if (!muzzle) - { - var go = new GameObject(MuzzleName); - go.layer = anchor.gameObject.layer; - muzzle = go.transform; - muzzle.SetParent(anchor.parent, false); - muzzle.localPosition = anchor.localPosition; - muzzle.localRotation = Quaternion.identity; - muzzle.localScale = Vector3.one; - } - - var link = new WeaponMods.ModLink(); - link.Slot = WeaponMod.Slot.Muzzle; - link.Location = muzzle; - links.Add(link); - return true; - } - - private static Type FindGameType(string name) - { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (var asm in assemblies) - { - try - { - var t = asm.GetType(name, false) ?? asm.GetType("Il2Cpp." + name, false); - if (t != null) - return t; - } - catch - { - } - } - - foreach (var asm in assemblies) - { - var asmName = asm.GetName().Name ?? string.Empty; - if (asm.IsDynamic || asmName.StartsWith("System", StringComparison.OrdinalIgnoreCase) || - asmName.StartsWith("Unity", StringComparison.OrdinalIgnoreCase) || - asmName.StartsWith("Il2Cppmscorlib", StringComparison.OrdinalIgnoreCase) || - asmName.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase) || - asmName.StartsWith("mscorlib", StringComparison.OrdinalIgnoreCase)) - continue; - - Type[] types; - try - { - types = asm.GetTypes(); - } - catch (ReflectionTypeLoadException e) - { - types = e.Types.Where(t => t != null).ToArray(); - } - catch - { - continue; - } - - foreach (var t in types) - { - if (t != null && t.Name == name) - return t; - } - } - return null; - } - - private static void InstallAudioPatches() - { - if (_audioHarmony != null) - return; - _audioHarmony = new HarmonyLib.Harmony("RifleScope.SuppressorAudio"); - - var handler = FindGameType("FMOD_AnimationEventHandler"); - var animPrefix = new HarmonyMethod(typeof(RifleScope).GetMethod(nameof(AnimEventPrefix), BindingFlags.NonPublic | BindingFlags.Static)); - var count = 0; - if (handler != null) - { - foreach (var m in handler.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)) - { - var ps = m.GetParameters(); - if (m.Name != "playFMODEvent" || ps.Length == 0 || ps[0].ParameterType != typeof(string)) - continue; - _audioHarmony.Patch(m, prefix: animPrefix); - count++; - } - } - - var common = FindGameType("FMODCommon"); - var oneshotPrefix = new HarmonyMethod(typeof(RifleScope).GetMethod(nameof(OneshotPrefix), BindingFlags.NonPublic | BindingFlags.Static)); - if (common != null) - { - foreach (var m in common.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly)) - { - var ps = m.GetParameters(); - if (m.Name != "PlayOneshotInternal" || ps.Length < 4 || ps[0].ParameterType != typeof(string)) - continue; - _audioHarmony.Patch(m, prefix: oneshotPrefix); - count++; - } - } - - RLog.Msg($"Suppressor audio: patched {count} methods (handler={(handler != null)}, common={(common != null)})"); - } - - private static bool RifleSuppressedNow() => - _suppressor && _rifle && _rifle.gameObject.activeInHierarchy; - - private static bool AnimEventPrefix(Component __instance, object[] __args) - { - try - { - if (!RifleSuppressedNow() || __args == null || __args.Length == 0 || __args[0] as string != RifleFireEvent) - return true; - if (!__instance || __instance.transform.root != _rifle.transform.root) - return true; - return false; - } - catch - { - return true; - } - } - - private static bool OneshotPrefix(MethodBase __originalMethod, object[] __args) - { - if (_reissuing) - return true; - - try - { - if (__args == null || __args.Length < 4 || __args[0] as string != SuppressedFireEvent) - return true; - - var existing = __args[3] as Il2CppReferenceArray; - - if (!RifleSuppressedNow()) - { - if (!_loggedPistolParams && existing != null && existing.Length > 0) - { - _loggedPistolParams = true; - RLog.Msg($"Suppressor audio: pistol params [{string.Join(", ", existing.Select(Describe))}]"); - } - return true; - } - - var oldCount = existing?.Length ?? 0; - for (var i = 0; i < oldCount; i++) - { - if (existing[i] != null && existing[i].ToString() == UpgradeParameter) - return true; - } - - var array = new Il2CppReferenceArray(oldCount + 2); - for (var i = 0; i < oldCount; i++) - array[i] = existing[i]; - array[oldCount] = new Il2CppSystem.Object(IL2CPP.ManagedStringToIl2Cpp(UpgradeParameter)); - array[oldCount + 1] = BoxFloat(SuppressedUpgradeValue); - - var args = (object[])__args.Clone(); - args[3] = array; - _reissuing = true; - try - { - __originalMethod.Invoke(null, args); - } - finally - { - _reissuing = false; - } - return false; - } - catch (Exception e) - { - if (!_loggedParamError) - { - _loggedParamError = true; - RLog.Error($"Suppressor audio: could not play suppressed shot: {(e.InnerException ?? e).Message}"); - } - return true; - } - } - - private static Il2CppSystem.Object BoxFloat(float value) - { - object single = new Il2CppSystem.Single(); - typeof(Il2CppSystem.Single).GetField("m_value", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(single, value); - return ((Il2CppSystem.Single)single).BoxIl2CppObject(); - } - - private static string Describe(Il2CppSystem.Object o) - { - if (o == null) - return "null"; - try - { - return $"{o.GetIl2CppType().Name}:{o.ToString()}"; - } - catch - { - return "?"; - } - } - - private static void SetSuppressor(RangedWeaponController rifle, bool on) - { - var weapon = rifle._rangedWeapon; - var mods = weapon ? weapon._weaponMods : null; - if (!mods) - { - Say("Suppressor: rifle has no WeaponMods"); - return; - } - - var mod = FindSuppressorMod(); - if (!mod) - { - Say("Suppressor: pistol suppressor mod is not loaded"); - return; - } - - if (on) - { - InstallAudioPatches(); - if (!EnsureMuzzleSlot(rifle, mods)) - { - Say("Suppressor: could not find the rifle muzzle"); - return; - } - - var applied = mods.HasMod(mod.ModItemId) || mods.ApplyMod(mod); - rifle._gunShotAudioEvent = SuppressedFireEvent; - RLog.Msg($"Suppressor on: applied={applied} slot={mod.AttachesToSlot} required={mod.RequiredSlot} item={mod.ModItemId}"); - return; - } - - if (mods.HasMod(mod.ModItemId)) - mods.RemoveMod(mod); - if (_originalFireEvent != null) - rifle._gunShotAudioEvent = _originalFireEvent; } private static Transform FindProjectileTransform(RangedWeaponController rifle) @@ -1000,24 +703,6 @@ public class RifleScope : SonsMod Say($"RifleScope is {(_enabled ? "on" : "off")}. Usage: scope on or scope off"); } - [DebugCommand("suppressor")] - private static void SuppressorCommand(string args) - { - args = (args ?? string.Empty).Trim().ToLowerInvariant(); - - if (args != "on" && args != "off") - { - Say($"Rifle suppressor is {(_suppressor ? "on" : "off")}. Usage: suppressor on or suppressor off"); - return; - } - - _suppressor = args == "on"; - Save(); - if (_rifle) - SetSuppressor(_rifle, _suppressor); - Say($"Rifle suppressor {(_suppressor ? "on" : "off")}"); - } - [DebugCommand("scopezoom")] private static void ScopeZoomCommand(string args) { @@ -1120,8 +805,6 @@ public class RifleScope : SonsMod _windCm = wind; if (parts.Length > 3) _enabled = parts[3] != "0"; - if (parts.Length > 4) - _suppressor = parts[4] == "1"; } catch (Exception e) { @@ -1133,7 +816,7 @@ public class RifleScope : SonsMod { try { - File.WriteAllText(_configPath, $"{_zoom.ToString(Inv)} {_trimCm.ToString(Inv)} {_windCm.ToString(Inv)} {(_enabled ? "1" : "0")} {(_suppressor ? "1" : "0")}"); + File.WriteAllText(_configPath, $"{_zoom.ToString(Inv)} {_trimCm.ToString(Inv)} {_windCm.ToString(Inv)} {(_enabled ? "1" : "0")}"); } catch (Exception e) { diff --git a/RifleScope.csproj b/RifleScope.csproj index 6e95103..2253753 100644 --- a/RifleScope.csproj +++ b/RifleScope.csproj @@ -6,7 +6,7 @@ latest RifleScope RifleScope - 1.1.1 + 1.2.0 CS0436;CS1701;CS1702;MSB3277;MSB3106 diff --git a/manifest.json b/manifest.json index 49f8c38..cfe69ab 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "id": "RifleScope", "name": "RifleScope", "author": "JustinOros", - "version": "1.1.1", + "version": "1.2.0", "description": "Stronger rifle scope magnification and a crosshair reticle", "gameVersion": "1.0.0", "platform": "Client",