using BepInEx; using BepInEx.Configuration; using System; using EFT; using EFT.UI; using UnityEngine; namespace UniformAim { [BepInPlugin("com.greg.tarkovuniformaim", "Uniform Aim for Tarkov", "1.0.0")] [BepInProcess("EscapeFromTarkov.exe")] public class Plugin : BaseUnityPlugin { //Bepinex.Configurator fields public static ConfigEntry configFOV; public static ConfigEntry configCoeff; public static ConfigEntry configSens; public static ConfigEntry configUseHFOV; public static ConfigEntry configEnableLogging; //TODO: figure out a way to read game settings to default the configFOV.Value to whatever the player has already set //TODO: figure out a way to read game settings to apply configSens.Value as a multiplier on top of Tarkov's stock sensitivity setting public static float mySens = 1f; public static float aimingSens; //sight data for hacky workarounds public static int SelectedScope = 0; public static int SelectedScopeMode = 0; public static bool isAiming = false; //human-friendly names for variables used later float FPSCameraFOV; float ScopeFOV; float currentFOV; //so we don't keep repeating ourselves float cachedFOV; string cachedDebugInfo; //Return aspect ratio based on game window resolution, currently unused but could be useful in the future float GetAspectRatio() { float resX = Convert.ToUInt16(Screen.width.ToString()); float resY = Convert.ToUInt16(Screen.height.ToString()); //string screenWidth = Screen.width.ToString(); //string screenHeight = Screen.height.ToString(); //float resX = Convert.ToUInt16(screenWidth); //float resY = Convert.ToUInt16(screenHeight); //Logger.LogInfo("GetAspectRatio(): resX: " + resX + " resY: " + resY); return (resX / resY); } //calculate horizontal FOV based on vertical FOV, currently unused but could be useful in the future float CalculateHFOV(float FOV) { float vFOVRad = FOV / 2 * Mathf.Deg2Rad; float hFOVRad = (float)(2 * Math.Atan(GetAspectRatio() * Math.Tan(vFOVRad))); if (configEnableLogging.Value) { Logger.LogInfo("Calculate HFOV: " + FOV + " Result: " + hFOVRad * Mathf.Rad2Deg); } return (float)(hFOVRad * Mathf.Rad2Deg); } //calculate sensitivity based on FOV difference and coefficient float CalculateSensitivity(float aimedFOV, float hipFOV) { //clamp to avoid invalid values aimedFOV = Mathf.Clamp(aimedFOV, 1f, 75f); hipFOV = Mathf.Clamp(hipFOV, 1f, 75f); //check if configUseHFOV is enabled, convert to horizontal degrees if true if(configUseHFOV.Value) { Logger.LogInfo("HFOV Hip: " + CalculateHFOV(hipFOV) + " HFOV Aim: " + CalculateHFOV(aimedFOV)); aimedFOV = CalculateHFOV(aimedFOV) / 2 * Mathf.Deg2Rad; hipFOV = CalculateHFOV(hipFOV) / 2 * Mathf.Deg2Rad; } else { aimedFOV = aimedFOV / 2 * Mathf.Deg2Rad; hipFOV = hipFOV / 2 * Mathf.Deg2Rad; } float exponent = (float)(100f / configCoeff.Value); float tanRatio = (float)(Math.Tan(aimedFOV) / Math.Tan(hipFOV)); float sensitivity = configSens.Value / 100f; float result = (float)Math.Pow(tanRatio, exponent) * sensitivity; return result; } //determine if the player is looking through the scope bool isScopeCameraActive() { if (Camera.allCamerasCount > 1) { return Camera.allCameras[1].GetComponent().isActiveAndEnabled; } return false; } //determine the correct FOV for calculations void DetermineCurrentFOV() { if (SelectedScope == 0 && isScopeCameraActive()) { currentFOV = ScopeFOV; } else {currentFOV = FPSCameraFOV; } //dirty fix for the Ultima MP-155 shotgun if(FPSCameraFOV > 35 && isScopeCameraActive() && ScopeFOV == 15) { currentFOV = FPSCameraFOV; } } void LogDebugInfo() { //preformatted debug info string string currentDebugInfo = $"Current FOV: {currentFOV} FPS Camera FOV: {FPSCameraFOV} Scope FOV: {ScopeFOV} Cached FOV: {cachedFOV} Sens: {mySens} SelectedScope: {SelectedScope}"; //only output currentDebugInfo if there were any changes if (currentDebugInfo != cachedDebugInfo) { Logger.LogInfo(currentDebugInfo); cachedDebugInfo = currentDebugInfo; } } void Awake() { new UpdateSensitivityPatch().Enable(); new get_AimingSensitivityPatch().Enable(); new get_SelectedScopeIndexPatch().Enable(); new get_SelectedScopeModePatch().Enable(); new get_IsAimingPatch().Enable(); //add configuration slider for field of view configFOV = Config.Bind("General", "FOV", 75, new ConfigDescription("In-game Field of View value", new AcceptableValueRange(51, 75))); //add coefficient slider configCoeff = Config.Bind("General", "Coefficient", 133, new ConfigDescription("Coefficient - increases sensitivity at higher zoom levels", new AcceptableValueRange(1, 300))); //add sensitivity slider configSens = Config.Bind("General", "Sensitivity", 25, new ConfigDescription("Sensitivity while aiming", new AcceptableValueRange(1, 200))); //use HFOV instead of VFOV for sensitivity calculations configUseHFOV = Config.Bind("General", "Use Horizontal FOV?", true, new ConfigDescription("Toggles between using Horizontal FOV and Vertical FOV for sensitivity calculations.")); //enable logging configEnableLogging = Config.Bind("Debug", "Enable logging", false, new ConfigDescription("Enables logging in BepInEx console, extremely spammy!")); } void FixedUpdate() { //FixedUpdate() at 50Hz (Unity default) tickrate appears to resolve the issue of this script breaking when AI spawns. Time.fixedDeltaTime = (1f/50f); if (isAiming) { //Grab FOV values for calculation FPSCameraFOV = Camera.allCameras[0].fieldOfView; if (Camera.allCamerasCount > 1) { ScopeFOV = Camera.allCameras[1].fieldOfView; } //Figure out if the FPSCamera is zoomed in, prevents the script from ticking while the player is healing //if(FPSCameraFOV < configFOV.Value) if(FPSCameraFOV < 75) { DetermineCurrentFOV(); //do not update sensitivity if currentFOV hasn't changed if (cachedFOV != currentFOV) { mySens = CalculateSensitivity(currentFOV, configFOV.Value); cachedFOV = currentFOV; } } } //draw debug info if (configEnableLogging.Value) { LogDebugInfo(); } } } }