using BepInEx; using BepInEx.Configuration; using Comfort.Common; using EFT; using EFT.Settings.Graphics; using System.Collections; using System.ComponentModel; using System.Reflection; using UnityEngine; /* Dependencies: * ../BepInEx/core/ * BepInEx.dll * ../EscapeFromTarkov_Data/Managed/ * Aki.Reflection.dll * Assembly-CSharp.dll * Comfort.dll * ItemComponent.Types.dll * System.dll * Unity.Postprocessing.Runtime.dll * UnityEngine.dll * UnityEngine.CoreModule.dll */ namespace ScopeTweaks { [BepInPlugin("com.notGreg.scopeTweaks", "notGreg's Scope Tweaks", "3.3.0")] public class Plugin : BaseUnityPlugin { ConfigEntry cameraResolutionScale; ConfigEntry scopeCameraResolutionScale; ConfigEntry scopeFixType; ConfigEntry enableDebug; enum EFOVScalingMode { Disabled, [Description("Magnified optics")] ScopesOnly, [Description("All sights")] All, } //The following assignments should allow for faster patching in the future. //The correct GClass can be found by searching for "ClearSettings" in Assembly-CSharp.dll via dnSpy, netFiddle, ilSpy, etc. //GClass1642 settingsLibrary = Singleton.Instance; //used by SPT-AKI 3.2.3 //GClass1653 settingsLibrary = Singleton.Instance; //used by SPT-AKI 3.3.0 //GClass1659 settingsLibrary = Singleton.Instance; //used by SPT-AKI 3.4.1 //The correct GClass can be found by searching for "SetFov" in Assembly-CSharp.dll via dnSpy, netFiddle, ilSpy, etc. //GClass1774 setFovLibrary = Singleton.Instance; //used by SPT-AKI 3.2.3 //GClass1785 setFovLibrary = Singleton.Instance; //used by SPT-AKI 3.3.0 CameraClass setFovLibrary = CameraClass.Instance; //used by SPT-AKI 3.4.1 void Awake() { cameraResolutionScale = Config.Bind( "General", "Main camera scale %", 80, new ConfigDescription("Additional override applied on top of currently enabled resolution scaling method.", new AcceptableValueRange(25, 100))); scopeCameraResolutionScale = Config.Bind( "General", "Scope camera scale %", 80, new ConfigDescription("Additional override applied on top of currently enabled resolution scaling method.", new AcceptableValueRange(25, 100))); scopeFixType = Config.Bind( "General", "High FOV sight tweak", EFOVScalingMode.ScopesOnly, new ConfigDescription("")); enableDebug = Config.Bind("Debug", "Enable debug logging", false); } void Update() { //Check if the game is in a valid state and execute logic depening on the status of the game if (Singleton.Instance == null) return; GameStatus currentGameState = Singleton.Instance.Status; //logic should only execute once per game instead of every frame if (!gameStateChanged(currentGameState)) return; switch (currentGameState) { case GameStatus.Started: { if (enableDebug.Value) Logger.LogInfo("Getting local player"); mainPlayer = getLocalPlayer(); subscribeHandsChangedEvent(); if (enableDebug.Value) Logger.LogInfo("Assigning cameras..."); StartCoroutine(tryGetMainCamera()); StartCoroutine(tryGetScopeCamera()); defaultInGameFOV = inGameFOV; break; } case GameStatus.SoftStopping: case GameStatus.Stopping: case GameStatus.Stopped: { if (enableDebug.Value) Logger.LogInfo("Resetting..."); FPSCamera = null; scopeCamera = null; mainPlayer = null; if (enableDebug.Value) Logger.LogInfo($"Restoring FOV in settings: {defaultInGameFOV}"); inGameFOV = defaultInGameFOV; break; } default: break; } } /// /// GAME STATUS /// GameStatus lastGameState; //compare current game status to the last saved game status, return true if game status has changed bool gameStateChanged(GameStatus currentState) { if (currentState == lastGameState) return false; lastGameState = currentState; return true; } Player mainPlayer; //find and return the player character in the session Player getLocalPlayer() { return Singleton.Instance.RegisteredPlayers.Find(p => p.IsYourPlayer); } /// /// FIELD OF VIEW /// /// private int defaultInGameFOV; private int inGameFOV { get { //int fov = settingsLibrary.Game.Settings.FieldOfView.Value; int fov = Singleton.Instance.Game.Settings.FieldOfView.Value; if (enableDebug.Value) Logger.LogInfo($"get_defaultInGameFOV: Reading from settings: {fov}"); return fov; } set { if (enableDebug.Value) Logger.LogInfo($"set_defaultInGameFOV: Writing to settings: {value}"); //settingsLibrary.Game.Settings.FieldOfView.Value = value; int fov = Singleton.Instance.Game.Settings.FieldOfView.Value; } } /// /// CAMERA SETUP /// Camera FPSCamera = null; Camera scopeCamera = null; SSAA ssaaInstance = null; FieldInfo _nextSSRation = null; WaitForSeconds myDelaySec = new WaitForSeconds(1); IEnumerator tryGetMainCamera() { string cameraName = "FPS Camera"; if (GameObject.Find(cameraName) != null) { FPSCamera = GameObject.Find(cameraName).GetComponent(); if (enableDebug.Value) Logger.LogInfo($"{FPSCamera.name} found!"); } else { if (enableDebug.Value) Logger.LogMessage($"Camera \"{cameraName}\" not found."); yield return myDelaySec; StartCoroutine(tryGetMainCamera()); yield break; } yield return null; } FieldInfo getFieldInfo(string fieldName) { return typeof(SSAA).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); } IEnumerator tryGetScopeCamera() { string cameraName = "BaseOpticCamera(Clone)"; if (GameObject.Find(cameraName) != null) { scopeCamera = GameObject.Find(cameraName).GetComponent(); if (enableDebug.Value) Logger.LogInfo($"Camera \"{scopeCamera.name}\" found!"); } yield break; } void setMainCameraResolutionScale(int value = 100) { //if (settingsLibrary.Graphics.Settings.DLSSEnabled) if (Singleton.Instance.Graphics.Settings.DLSSEnabled) { if (enableDebug.Value) Logger.LogInfo("DLSS enabled - skipping MainCamera resolution change"); return; } if (enableDebug.Value) Logger.LogInfo($"Setting MainCam res scale to {value}%"); ssaaInstance = FPSCamera.GetComponent(); _nextSSRation = getFieldInfo("_nextSSRation"); _nextSSRation.SetValue(ssaaInstance, (float)(currentScalingFactor * value / 100f)); } void setScopeCameraResolutionScale(int value) { if (scopeCamera == null || !scopeCamera.isActiveAndEnabled) { if (enableDebug.Value) Logger.LogInfo("ScopeCam inactive or absent!"); return; } if (enableDebug.Value) Logger.LogInfo($"Setting Scope res scale to {value}%"); scopeCamera.GetComponent().OpticCameraToMainCameraResolutionRatio = (float)(currentScalingFactor * (value / 100f)); } /// /// PLAYER WEAPON EVENTS /// void subscribeHandsChangedEvent() { if (enableDebug.Value) Logger.LogInfo("Subscribing to HandsChanged Event"); mainPlayer.HandsChangedEvent += (handsArgs) => { subscribeOnAimingChangedEvent(); }; } void subscribeOnAimingChangedEvent() { if (enableDebug.Value) Logger.LogInfo("Subscribing to OnAimingChanged Event"); mainPlayer.HandsController.OnAimingChanged += (aimingArgs) => { currentScalingFactor = getCurrentScalingFactor(); StartCoroutine(tryGetScopeCamera()); if (!mainPlayer.ProceduralWeaponAnimation.IsAiming) { switch (scopeFixType.Value) { case EFOVScalingMode.Disabled: { if (enableDebug.Value) Logger.LogInfo($"Switch: Not Aiming: ScalingMode: {scopeFixType.Value}"); break; } case EFOVScalingMode.ScopesOnly: { if (enableDebug.Value) Logger.LogInfo($"Switch: Not Aiming: ScalingMode: {scopeFixType.Value}"); setMainCameraResolutionScale(); setFovLibrary.SetFov(defaultInGameFOV, 0.33f, false); inGameFOV = defaultInGameFOV; break; } case EFOVScalingMode.All: { if (enableDebug.Value) Logger.LogInfo($"Switch: Not Aiming: ScalingMode: {scopeFixType.Value}"); setMainCameraResolutionScale(); setFovLibrary.SetFov(defaultInGameFOV, 0.33f, false); inGameFOV = defaultInGameFOV; break; } } return; } if (mainPlayer.ProceduralWeaponAnimation.IsAiming) { defaultInGameFOV = inGameFOV; switch (scopeFixType.Value) { case EFOVScalingMode.Disabled: { if (enableDebug.Value) Logger.LogInfo($"Switch: Aiming: ScalingMode: {scopeFixType.Value}"); break; } case EFOVScalingMode.ScopesOnly: { if (enableDebug.Value) Logger.LogInfo($"Switch: Aiming: ScalingMode: {scopeFixType.Value}"); if (scopeCamera.isActiveAndEnabled) { if (enableDebug.Value) Logger.LogInfo("ScopeCamera not found or disabled!"); applyFixesWhileAiming(); } break; } case EFOVScalingMode.All: { if (enableDebug.Value) Logger.LogInfo($"Switch: Aiming: ScalingMode: {scopeFixType.Value}"); applyFixesWhileAiming(); break; } } } }; } void applyFixesWhileAiming() { if (enableDebug.Value) Logger.LogInfo($"AimingSpeed: {mainPlayer.ProceduralWeaponAnimation.AimingSpeed}"); if (enableDebug.Value) Logger.LogInfo("Changing FOV while aiming"); setFovLibrary.SetFov(35, 0.25f, false); //SPT-AKI 3.3.0 inGameFOV = 50; if (enableDebug.Value) Logger.LogInfo("Updating scope resolution"); setMainCameraResolutionScale(cameraResolutionScale.Value); setScopeCameraResolutionScale(scopeCameraResolutionScale.Value); } /// /// IN-GAME SETTINGS /// float currentScalingFactor = 1.0f; float getCurrentScalingFactor() { if (enableDebug.Value) Logger.LogInfo("Getting current scaling factor:"); //var graphics = settingsLibrary.Graphics.Settings; //SPT-AKI 3.3.0 var graphics = Singleton.Instance.Graphics.Settings; if (!graphics.DLSSEnabled && !graphics.FSREnabled) { if (enableDebug.Value) Logger.LogInfo($"Supersampling factor: {graphics.SuperSamplingFactor}"); return graphics.SuperSamplingFactor; } if (graphics.DLSSEnabled) { float DLSSFactor = 1.0f; switch (graphics.DLSSMode.Value) { case EDLSSMode.Off: { DLSSFactor = 1.0f; break; } case EDLSSMode.Quality: { DLSSFactor = 0.67f; break; } case EDLSSMode.Balanced: { DLSSFactor = 0.58f; break; } case EDLSSMode.Performance: { DLSSFactor = 0.5f; break; } case EDLSSMode.UltraPerformance: { DLSSFactor = 0.33f; break; } } if (enableDebug.Value) Logger.LogInfo($"DLSS factor: {DLSSFactor}"); return DLSSFactor; } if (graphics.FSREnabled) { float FSRFactor = 1.0f; switch (graphics.FSRMode.Value) { case EFSRMode.Off: { FSRFactor = 1.0f; break; } case EFSRMode.UltraQuality: { FSRFactor = 0.77f; break; } case EFSRMode.Quality: { FSRFactor = 0.66f; break; } case EFSRMode.Balanced: { FSRFactor = 0.59f; break; } case EFSRMode.Performance: { FSRFactor = 0.5f; break; } } if (enableDebug.Value) Logger.LogInfo($"FSR factor: {FSRFactor}"); return FSRFactor; } if (enableDebug.Value) Logger.LogInfo($"GetCurrentScalingFactor(): Something went wrong. Returning 1.0f"); return 1.0f; } } }