diff --git a/RifleScope.cs b/RifleScope.cs deleted file mode 100644 index 5e97346..0000000 --- a/RifleScope.cs +++ /dev/null @@ -1,1158 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using HarmonyLib; -using Il2CppInterop.Runtime; -using Il2CppInterop.Runtime.InteropTypes.Arrays; -using RedLoader; -using RedLoader.Utils; -using Sons.FieldOfView; -using Sons.Items.Core; -using Sons.Weapon; -using SonsSdk; -using SonsSdk.Attributes; -using TheForest.Utils; -using TMPro; -using UnityEngine; -using UnityEngine.UI; - -namespace RifleScope; - -public class RifleScope : SonsMod -{ - private const float DefaultZoom = 5f; - private const float MinZoom = 1f; - private const float MaxZoom = 50f; - private const float DefaultTrimCm = 60f; - 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; - private const float MinDotSpacingPx = 14f; - private const float ThinLinePx = 2f; - private const float PostPx = 8f; - private const string RedDotShader = "Sons/RedDot HLSL"; - private const int MaskSize = 1024; - private const float MaxRayDistance = 2000f; - - private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; - private static readonly Dictionary OriginalOffsets = new(); - private static readonly Dictionary HiddenMaterials = new(); - private static readonly HashSet HiddenIds = new(); - - private static float _zoom = DefaultZoom; - private static float _trimCm = DefaultTrimCm; - 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 WeaponMod _suppressorMod; - private static float _hipFov; - private static float _vanillaAimFov = 35f; - private static float _targetAimFov; - private static int _heldLayer = -1; - private static Camera _hiddenCam; - private static Transform _projectile; - private static ProjectileTransformConstraint _steeredConstraint; - private static Quaternion _originalShotOffset; - private static bool _steered; - - private static GameObject _overlay; - private static RectTransform _scopeRect; - private static RectTransform _crosshairRect; - private static RectTransform _lineH; - private static RectTransform _lineV; - private static readonly List<(RectTransform Rect, Vector2 Dir)> Posts = new(); - private static float _layoutH = -1f; - private static readonly List<(RectTransform Rect, Vector2 Dir)> MilDots = new(); - private static RectTransform _leftBar; - private static RectTransform _rightBar; - private static TextMeshProUGUI _rangeText; - private static string _lastRange; - private static int _milsPerDot = 1; - - public RifleScope() - { - OnUpdateCallback = OnUpdate; - HarmonyPatchAll = true; - } - - protected override void OnSdkInitialized() - { - _configPath = Path.Combine(LoaderEnvironment.UserDataDirectory, "RifleScope.txt"); - Load(); - RLog.Msg($"RifleScope loaded. Zoom {Fmt(_zoom)}x the vanilla scope. Change it with: scopezoom <{Fmt(MinZoom)}-{Fmt(MaxZoom)}>"); - } - - private void OnUpdate() - { - try - { - Tick(); - } - catch (Exception e) - { - RLog.Error($"RifleScope error: {e.Message}"); - _rifle = null; - SetZoomed(false, null); - } - } - - private static void Tick() - { - if (!_rifle || !_rifle.gameObject.activeInHierarchy) - { - if (_rifle || _hiddenCam) - { - _rifle = null; - SetZoomed(false, null); - } - - var pending = _pendingRifle; - _pendingRifle = null; - if (!pending || !pending.gameObject.activeInHierarchy) - return; - AdoptRifle(pending); - } - - if (!_enabled) - return; - - var manager = _rifle._cameraFovManager; - if (!manager) - { - SetZoomed(false, null); - return; - } - - UpdateOffset(manager); - - var cam = manager._targetCamera; - var zoomed = _rifle.IsAiming && cam && _zoom > 1.01f && cam.fieldOfView < _vanillaAimFov - 0.5f; - SetZoomed(zoomed, cam); - } - - internal static void OnWeaponTick(RangedWeaponController controller) - { - if (!controller || (_rifle && _rifle.Pointer == controller.Pointer)) - return; - var rifle = controller.TryCast(); - if (rifle && rifle.IsLocalPlayer()) - _pendingRifle = rifle; - } - - private static void AdoptRifle(RifleAnimatorController rifle) - { - _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; - 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) - { - var constraint = rifle._projectileTransformConstraint; - if (constraint) - return constraint.transform; - foreach (var t in rifle.GetComponentsInChildren(true)) - { - if (t.name == "ProjectileTransform") - return t; - } - return null; - } - - private static bool CenterPoint(Camera cam, out Vector3 point) - { - var origin = cam.transform.position; - var dir = cam.transform.forward; - var mask = _rifle ? _rifle._aimHitMask.value : 0; - if (mask == 0) - mask = Physics.DefaultRaycastLayers; - - if (Physics.Raycast(origin, dir, out var hit, MaxRayDistance, mask, QueryTriggerInteraction.Ignore)) - { - point = hit.point; - return true; - } - - point = origin + dir * MaxRayDistance; - return false; - } - - private static float Range(Camera cam) - { - if (!cam || !CenterPoint(cam, out var point)) - return -1f; - return Vector3.Distance(cam.transform.position, point); - } - - private static void SteerShot(Camera cam) - { - var constraint = _rifle._projectileTransformConstraint; - if (!constraint || !_projectile) - return; - - var goal = constraint._target; - if (!goal) - return; - - if (!_steered || _steeredConstraint.Pointer != constraint.Pointer) - { - RestoreShot(); - _steeredConstraint = constraint; - _originalShotOffset = constraint._offset; - _steered = true; - } - - CenterPoint(cam, out var point); - var scale = Vector3.Distance(cam.transform.position, point) / TrimReferenceRange; - point -= cam.transform.up * (_trimCm * 0.01f * scale); - point += cam.transform.right * (_windCm * 0.01f * scale); - var dir = point - _projectile.position; - if (dir.sqrMagnitude < 0.0001f) - return; - - var desired = Quaternion.LookRotation(dir, goal.up); - constraint._offset = Quaternion.Inverse(goal.rotation) * desired; - } - - private static string DebugLine(Camera cam) - { - if (!_projectile || !CenterPoint(cam, out var point)) - return $"trim {Fmt(_trimCm)} cm\nno target"; - - var r = Vector3.Distance(_projectile.position, point); - var barrel = _projectile.position + _projectile.forward * r; - var up = Vector3.Dot(barrel - point, cam.transform.up) * 100f; - var side = Vector3.Dot(barrel - point, cam.transform.right) * 100f; - var eye = Vector3.Dot(_projectile.position - cam.transform.position, cam.transform.up) * 100f; - return $"trim {Fmt(_trimCm)} wind {Fmt(_windCm)} steer {(_steered ? "on" : "off")}\nbarrel up {up:+0.0;-0.0} cm side {side:+0.0;-0.0} cm\nbarrel above eye {eye:+0.0;-0.0} cm"; - } - - private static void RestoreShot() - { - if (_steered && _steeredConstraint) - _steeredConstraint._offset = _originalShotOffset; - _steered = false; - _steeredConstraint = null; - } - - private static void UpdateOffset(FovManager manager) - { - var settings = _rifle._fovChangeSettings; - if (settings == null) - return; - - if (!OriginalOffsets.TryGetValue(settings.Pointer, out var original)) - { - original = settings._FieldOfViewTargetOffset_k__BackingField; - OriginalOffsets[settings.Pointer] = original; - } - - if (!_rifle.IsAiming && manager._activeSource == null) - _hipFov = manager._currentTargetFieldOfView; - if (_hipFov <= 1f) - _hipFov = manager._defaultFov; - - _vanillaAimFov = Mathf.Clamp(_hipFov + original, 1f, 179f); - - if (_rifle.IsAiming) - return; - - var target = 2f * Mathf.Atan(Mathf.Tan(_vanillaAimFov * 0.5f * Mathf.Deg2Rad) / _zoom) * Mathf.Rad2Deg; - _targetAimFov = target; - var offset = target - _hipFov; - if (Mathf.Abs(settings._FieldOfViewTargetOffset_k__BackingField - offset) > 0.001f) - settings._FieldOfViewTargetOffset_k__BackingField = offset; - } - - private static void HideRedDot(RifleAnimatorController rifle) - { - foreach (var renderer in rifle.GetComponentsInChildren(true)) - { - var mats = renderer.sharedMaterials; - var changed = false; - for (var i = 0; i < mats.Length; i++) - { - var mat = mats[i]; - if (!mat || HiddenIds.Contains(mat.GetInstanceID()) || !mat.shader || mat.shader.name != RedDotShader) - continue; - RedDotSlots.Add((renderer, i, mat)); - mats[i] = HiddenFor(mat); - changed = true; - } - if (changed) - renderer.sharedMaterials = mats; - } - } - - private static void RestoreRedDot() - { - foreach (var (renderer, slot, original) in RedDotSlots) - { - if (!renderer || !original) - continue; - var mats = renderer.sharedMaterials; - if (slot >= mats.Length || !mats[slot] || !HiddenIds.Contains(mats[slot].GetInstanceID())) - continue; - mats[slot] = original; - renderer.sharedMaterials = mats; - } - RedDotSlots.Clear(); - } - - private static void Disable() - { - SetZoomed(false, null); - if (_rifle) - { - var settings = _rifle._fovChangeSettings; - if (settings != null && OriginalOffsets.TryGetValue(settings.Pointer, out var original)) - settings._FieldOfViewTargetOffset_k__BackingField = original; - } - RestoreRedDot(); - } - - private static Material HiddenFor(Material original) - { - var id = original.GetInstanceID(); - if (HiddenMaterials.TryGetValue(id, out var cached) && cached) - return cached; - - var hidden = new Material(original) - { - name = original.name + "_Hidden", - hideFlags = HideFlags.HideAndDontSave - }; - hidden.SetColor("_Color", Color.clear); - if (hidden.HasProperty("_EmissionColor")) - hidden.SetColor("_EmissionColor", Color.clear); - - HiddenMaterials[id] = hidden; - HiddenIds.Add(hidden.GetInstanceID()); - return hidden; - } - - private static void SetZoomed(bool zoomed, Camera cam) - { - if (zoomed) - { - if (_heldLayer < 0) - { - _heldLayer = LayerMask.NameToLayer("Held"); - if (_heldLayer < 0) - _heldLayer = 15; - } - - if (_hiddenCam && _hiddenCam != cam) - ShowHeld(); - if (!_hiddenCam) - { - cam.cullingMask &= ~(1 << _heldLayer); - _hiddenCam = cam; - } - - SteerShot(cam); - ShowOverlay(true, Range(cam), _debug ? DebugLine(cam) : null, _targetAimFov > 0f ? _targetAimFov : cam.fieldOfView); - return; - } - - RestoreShot(); - if (_hiddenCam) - ShowHeld(); - _hiddenCam = null; - ShowOverlay(false, -1f, null, 0f); - } - - private static void ShowHeld() - { - if (_hiddenCam) - _hiddenCam.cullingMask |= 1 << _heldLayer; - _hiddenCam = null; - } - - private static void ShowOverlay(bool show, float range, string debug, float fov) - { - if (!show) - { - if (_overlay && _overlay.activeSelf) - _overlay.SetActive(false); - return; - } - - if (!_overlay) - CreateOverlay(); - if (!_overlay.activeSelf) - _overlay.SetActive(true); - - var h = (float)Screen.height; - if (!Mathf.Approximately(h, _layoutH)) - { - _layoutH = h; - LayoutReticle(h); - } - PlaceMilDots(h, fov); - - if (_rangeText) - { - var label = range < 0f ? "--- m" : $"{Mathf.RoundToInt(range)} m"; - if (debug != null) - label = debug + "\n" + label; - if (label != _lastRange) - { - _lastRange = label; - _rangeText.text = label; - } - } - } - - private static void LayoutReticle(float h) - { - var radius = h * 0.495f; - var postStart = radius * 0.55f; - var postLength = h - postStart; - - _scopeRect.sizeDelta = new Vector2(h, h); - _crosshairRect.sizeDelta = new Vector2(h * 2f, h * 2f); - _crosshairRect.anchoredPosition = Vector2.zero; - - _lineH.sizeDelta = new Vector2(h * 2f, ThinLinePx); - _lineV.sizeDelta = new Vector2(ThinLinePx, h * 2f); - - foreach (var (rect, dir) in Posts) - { - if (!rect) - continue; - var center = postStart + postLength * 0.5f; - rect.anchoredPosition = dir * center; - rect.sizeDelta = dir.x != 0f ? new Vector2(postLength, PostPx) : new Vector2(PostPx, postLength); - } - - if (_rangeText) - { - var rt = _rangeText.rectTransform; - rt.anchoredPosition = new Vector2(-0.34f * h, -0.31f * h); - rt.sizeDelta = new Vector2(0.3f * h, 0.2f * h); - _rangeText.fontSize = 0.035f * h; - } - - _leftBar.offsetMax = new Vector2(-h * 0.5f + 1f, 0f); - _rightBar.offsetMin = new Vector2(h * 0.5f - 1f, 0f); - } - - private static void CreateOverlay() - { - _overlay = new GameObject("RifleScopeOverlay"); - UnityEngine.Object.DontDestroyOnLoad(_overlay); - _overlay.hideFlags = HideFlags.HideAndDontSave; - - var canvas = _overlay.AddComponent(); - canvas.renderMode = RenderMode.ScreenSpaceOverlay; - canvas.sortingOrder = -100; - - var crosshairGo = new GameObject("Crosshair"); - crosshairGo.hideFlags = HideFlags.HideAndDontSave; - crosshairGo.transform.SetParent(_overlay.transform, false); - _crosshairRect = crosshairGo.AddComponent(); - Center(_crosshairRect); - - _lineH = CreateImage("LineH", Texture2D.whiteTexture, Color.black, _crosshairRect).rectTransform; - Center(_lineH); - _lineV = CreateImage("LineV", Texture2D.whiteTexture, Color.black, _crosshairRect).rectTransform; - Center(_lineV); - - Posts.Clear(); - foreach (var dir in new[] { Vector2.up, Vector2.down, Vector2.left, Vector2.right }) - { - var post = CreateImage($"Post{dir}", Texture2D.whiteTexture, Color.black, _crosshairRect).rectTransform; - Center(post); - Posts.Add((post, dir)); - } - - CreateMilDots(); - - var scope = CreateImage("Scope", BuildMaskTexture(), Color.white, _overlay.transform); - _scopeRect = scope.rectTransform; - Center(_scopeRect); - - _rangeText = CreateRangeText(scope.transform); - - _leftBar = CreateImage("Left", Texture2D.whiteTexture, Color.black, _overlay.transform).rectTransform; - _leftBar.anchorMin = new Vector2(0f, 0f); - _leftBar.anchorMax = new Vector2(0.5f, 1f); - _leftBar.offsetMin = Vector2.zero; - - _rightBar = CreateImage("Right", Texture2D.whiteTexture, Color.black, _overlay.transform).rectTransform; - _rightBar.anchorMin = new Vector2(0.5f, 0f); - _rightBar.anchorMax = new Vector2(1f, 1f); - _rightBar.offsetMax = Vector2.zero; - - _layoutH = -1f; - _overlay.SetActive(false); - } - - private static void Center(RectTransform rt) - { - rt.anchorMin = new Vector2(0.5f, 0.5f); - rt.anchorMax = new Vector2(0.5f, 0.5f); - rt.pivot = new Vector2(0.5f, 0.5f); - rt.anchoredPosition = Vector2.zero; - } - - private static void CreateMilDots() - { - MilDots.Clear(); - var tex = BuildDotTexture(); - foreach (var dir in new[] { Vector2.up, Vector2.down, Vector2.left, Vector2.right }) - { - for (var k = 1; k <= MilDotCount; k++) - { - var rt = CreateImage($"MilDot{dir}{k}", tex, Color.black, _crosshairRect).rectTransform; - Center(rt); - MilDots.Add((rt, dir * k)); - } - } - } - - private static void PlaceMilDots(float h, float fov) - { - if (fov <= 0f) - return; - - var pxPerMil = h * 0.5f / Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad) * 0.001f; - _milsPerDot = 1; - foreach (var step in new[] { 1, 2, 5, 10 }) - { - _milsPerDot = step; - if (pxPerMil * step >= MinDotSpacingPx) - break; - } - - var spacing = pxPerMil * _milsPerDot; - var size = Math.Max(MinDotPx, MilDotSize * spacing); - var limit = h * 0.495f * 0.95f; - - foreach (var (rect, dir) in MilDots) - { - if (!rect) - continue; - var pos = dir * spacing; - var visible = pos.magnitude <= limit; - if (rect.gameObject.activeSelf != visible) - rect.gameObject.SetActive(visible); - if (!visible) - continue; - rect.anchoredPosition = pos; - rect.sizeDelta = new Vector2(size, size); - } - } - - private static Texture2D BuildDotTexture() - { - const int size = 64; - var pixels = new Color32[size * size]; - var c = (size - 1) * 0.5f; - var radius = size * 0.5f - 1f; - for (var y = 0; y < size; y++) - { - for (var x = 0; x < size; x++) - { - var dx = x - c; - var dy = y - c; - var d = MathF.Sqrt(dx * dx + dy * dy); - var alpha = Math.Clamp(radius - d + 0.5f, 0f, 1f); - pixels[y * size + x] = new Color32(255, 255, 255, (byte)(alpha * 255f + 0.5f)); - } - } - return MakeTexture("RifleScopeMilDot", size, pixels); - } - - private static TextMeshProUGUI CreateRangeText(Transform parent) - { - try - { - var go = new GameObject("Range"); - go.hideFlags = HideFlags.HideAndDontSave; - go.transform.SetParent(parent, false); - var text = go.AddComponent(); - var font = FindFont(); - if (font) - text.font = font; - text.color = new Color(0.9f, 0.08f, 0.08f, 1f); - text.alignment = TextAlignmentOptions.BottomLeft; - text.enableWordWrapping = false; - text.raycastTarget = false; - text.text = string.Empty; - - var rt = text.rectTransform; - rt.anchorMin = new Vector2(0.5f, 0.5f); - rt.anchorMax = new Vector2(0.5f, 0.5f); - rt.pivot = Vector2.zero; - return text; - } - catch (Exception e) - { - RLog.Error($"RifleScope could not create the range readout: {e.Message}"); - return null; - } - } - - private static TMP_FontAsset FindFont() - { - try - { - var font = TMP_Settings.defaultFontAsset; - if (font) - return font; - } - catch - { - } - - foreach (var font in Resources.FindObjectsOfTypeAll()) - { - if (font) - return font; - } - return null; - } - - private static RawImage CreateImage(string name, Texture texture, Color color, Transform parent) - { - var go = new GameObject(name); - go.hideFlags = HideFlags.HideAndDontSave; - go.transform.SetParent(parent, false); - var image = go.AddComponent(); - image.texture = texture; - image.color = color; - image.raycastTarget = false; - return image; - } - - private static Texture2D BuildMaskTexture() - { - var size = MaskSize; - var pixels = new Color32[size * size]; - var c = (size - 1) * 0.5f; - var radius = size * 0.495f; - var fadeStart = radius * 0.88f; - - for (var y = 0; y < size; y++) - { - for (var x = 0; x < size; x++) - { - var dx = x - c; - var dy = y - c; - var d = MathF.Sqrt(dx * dx + dy * dy); - var alpha = Math.Clamp((d - radius) * 0.5f + 0.5f, 0f, 1f); - var t = Math.Clamp((d - fadeStart) / (radius - fadeStart), 0f, 1f); - var fade = t * t * (3f - 2f * t) * 0.55f; - if (fade > alpha) - alpha = fade; - pixels[y * size + x] = new Color32(0, 0, 0, (byte)(alpha * 255f + 0.5f)); - } - } - - return MakeTexture("RifleScopeMask", size, pixels); - } - - private static Texture2D MakeTexture(string name, int size, Color32[] pixels) - { - var tex = new Texture2D(size, size, TextureFormat.RGBA32, false) - { - name = name, - wrapMode = TextureWrapMode.Clamp, - filterMode = FilterMode.Bilinear, - hideFlags = HideFlags.HideAndDontSave - }; - tex.SetPixels32(pixels); - tex.Apply(false, false); - return tex; - } - - [DebugCommand("scope")] - private static void ScopeCommand(string args) - { - args = (args ?? string.Empty).Trim().ToLowerInvariant(); - - if (args == "on" || args == "off") - { - _enabled = args == "on"; - Save(); - if (_enabled) - _rifle = null; - else - Disable(); - Say($"RifleScope {(_enabled ? "on" : "off")}"); - return; - } - - 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) - { - args = (args ?? string.Empty).Trim(); - - if (args.Length == 0) - { - Say($"Scope zoom is {Fmt(_zoom)}x the vanilla scope. Usage: scopezoom <{Fmt(MinZoom)}-{Fmt(MaxZoom)}>"); - return; - } - - if (!float.TryParse(args, NumberStyles.Float, Inv, out var value) || value < MinZoom || value > MaxZoom) - { - Say($"Usage: scopezoom <{Fmt(MinZoom)}-{Fmt(MaxZoom)}>"); - return; - } - - _zoom = value; - Save(); - Say($"Scope zoom set to {Fmt(_zoom)}x the vanilla scope. Applies the next time you aim."); - } - - [DebugCommand("scopetrim")] - private static void ScopeTrimCommand(string args) - { - args = (args ?? string.Empty).Trim(); - - if (args.Length == 0) - { - Say($"Scope trim is {Fmt(_trimCm)}. Positive moves hits down, negative moves hits up. Usage: scopetrim <-{Fmt(MaxTrimCm)} to {Fmt(MaxTrimCm)}>"); - return; - } - - if (!float.TryParse(args, NumberStyles.Float, Inv, out var value) || value < -MaxTrimCm || value > MaxTrimCm) - { - Say($"Usage: scopetrim <-{Fmt(MaxTrimCm)} to {Fmt(MaxTrimCm)}>"); - return; - } - - _trimCm = value; - Save(); - Say($"Scope trim set to {Fmt(_trimCm)}."); - } - - [DebugCommand("scopewind")] - private static void ScopeWindCommand(string args) - { - args = (args ?? string.Empty).Trim(); - - if (args.Length == 0) - { - Say($"Scope windage is {Fmt(_windCm)}. Positive moves hits right, negative moves hits left. Usage: scopewind <-{Fmt(MaxWindCm)} to {Fmt(MaxWindCm)}>"); - return; - } - - if (!float.TryParse(args, NumberStyles.Float, Inv, out var value) || value < -MaxWindCm || value > MaxWindCm) - { - Say($"Usage: scopewind <-{Fmt(MaxWindCm)} to {Fmt(MaxWindCm)}>"); - return; - } - - _windCm = value; - Save(); - Say($"Scope windage set to {Fmt(_windCm)}."); - } - - [DebugCommand("scopedebug")] - private static void ScopeDebugCommand(string args) - { - _debug = !_debug; - Say($"Scope debug readout {(_debug ? "on" : "off")}"); - } - - private static void Say(string text) - { - RLog.Msg(text); - try - { - SonsTools.ShowMessage(text, 5f); - } - catch - { - } - } - - private static string Fmt(float v) => v.ToString("0.##", Inv); - - private static void Load() - { - try - { - if (!File.Exists(_configPath)) - return; - var parts = File.ReadAllText(_configPath).Split(new[] { ' ', '\r', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length > 0 && float.TryParse(parts[0], NumberStyles.Float, Inv, out var zoom) && zoom >= MinZoom && zoom <= MaxZoom) - _zoom = zoom; - if (parts.Length > 1 && float.TryParse(parts[1], NumberStyles.Float, Inv, out var trim) && trim >= -MaxTrimCm && trim <= MaxTrimCm) - _trimCm = trim; - if (parts.Length > 2 && float.TryParse(parts[2], NumberStyles.Float, Inv, out var wind) && wind >= -MaxWindCm && wind <= MaxWindCm) - _windCm = wind; - if (parts.Length > 3) - _enabled = parts[3] != "0"; - if (parts.Length > 4) - _suppressor = parts[4] == "1"; - } - catch (Exception e) - { - RLog.Error($"RifleScope could not read config: {e.Message}"); - } - } - - private static void Save() - { - try - { - File.WriteAllText(_configPath, $"{_zoom.ToString(Inv)} {_trimCm.ToString(Inv)} {_windCm.ToString(Inv)} {(_enabled ? "1" : "0")} {(_suppressor ? "1" : "0")}"); - } - catch (Exception e) - { - RLog.Error($"RifleScope could not write config: {e.Message}"); - } - } -} - -[HarmonyPatch(typeof(RangedWeaponController), nameof(RangedWeaponController.CheckFireInput))] -internal static class CheckFireInputPatch -{ - private static void Postfix(RangedWeaponController __instance) - { - try - { - global::RifleScope.RifleScope.OnWeaponTick(__instance); - } - catch - { - } - } -} \ No newline at end of file diff --git a/RifleScope.csproj b/RifleScope.csproj index 74724bb..6e95103 100644 --- a/RifleScope.csproj +++ b/RifleScope.csproj @@ -6,7 +6,7 @@ latest RifleScope RifleScope - 1.1.0 + 1.1.1 CS0436;CS1701;CS1702;MSB3277;MSB3106 diff --git a/manifest.json b/manifest.json index 0245f24..49f8c38 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "id": "RifleScope", "name": "RifleScope", "author": "JustinOros", - "version": "1.1.0", + "version": "1.1.1", "description": "Stronger rifle scope magnification and a crosshair reticle", "gameVersion": "1.0.0", "platform": "Client",