Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
796e898deb | ||
|
|
a5f241c1b3 | ||
|
|
b3aecc2bd5 | ||
|
|
7ac7c41645 |
No files matched your search
@@ -117,7 +117,6 @@ try {
|
|||||||
Write-Host 'Start Sons of the Forest, then:'
|
Write-Host 'Start Sons of the Forest, then:'
|
||||||
Write-Host ' 1. Check that MODS appears on the main menu and lists RifleScope'
|
Write-Host ' 1. Check that MODS appears on the main menu and lists RifleScope'
|
||||||
Write-Host ' 2. Load into a game, equip the rifle and aim down the scope'
|
Write-Host ' 2. Load into a game, equip the rifle and aim down the scope'
|
||||||
Write-Host ' 3. Press F1 and type scopezoom to see or change the zoom'
|
|
||||||
Write-Host ''
|
Write-Host ''
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -129,4 +128,3 @@ catch {
|
|||||||
Write-Host ''
|
Write-Host ''
|
||||||
}
|
}
|
||||||
|
|
||||||
Read-Host 'Press Enter to close' | Out-Null
|
|
||||||
@@ -1,206 +1,114 @@
|
|||||||
\# Rifle Scope
|
# Rifle Scope
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A stronger rifle scope for Sons of the Forest. Aiming down the rifle scope zooms
|
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
|
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.
|
## Multiplayer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
\## Multiplayer
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Client side only. Nothing gets installed on a dedicated server, and other players
|
Client side only. Nothing gets installed on a dedicated server, and other players
|
||||||
|
|
||||||
do not need the mod.
|
do not need the mod.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Easy install
|
||||||
\## Installation
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
\### Easy install
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Close the game, open PowerShell and paste:
|
Close the game, open PowerShell and paste:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
|
||||||
irm https://raw.githubusercontent.com/JustinOros/sotf-riflescope/main/Install.ps1 | iex
|
irm https://raw.githubusercontent.com/JustinOros/sotf-riflescope/main/Install.ps1 | iex
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
It finds your game, installs RedLoader if needed, and installs the latest
|
It finds your game, installs RedLoader if needed, and installs the latest
|
||||||
|
|
||||||
RifleScope. Run it again any time to update.
|
RifleScope. Run it again any time to update.
|
||||||
|
|
||||||
|
### Manual install
|
||||||
|
|
||||||
|
#### Step 1: Install RedLoader
|
||||||
|
|
||||||
\### Manual install
|
[RedLoader](https://github.com/ToniMacaroni/RedLoader/releases/latest) is the mod
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
\#### 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
|
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.
|
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 RifleScope
|
||||||
|
|
||||||
1\. Download `RedLoader.zip` from the
|
1. Download `RifleScope.zip` from the
|
||||||
|
[latest release](https://github.com/JustinOros/sotf-riflescope/releases/latest)
|
||||||
  \[latest RedLoader release](https://github.com/ToniMacaroni/RedLoader/releases/latest)
|
2. Extract it into the `Mods` folder inside your game folder
|
||||||
|
3. You should end up with:
|
||||||
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 RifleScope
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
1\. Download `RifleScope.zip` from the
|
|
||||||
|
|
||||||
  \[latest release](https://github.com/JustinOros/sotf-riflescope/releases/latest)
|
|
||||||
|
|
||||||
2\. Extract it into the `Mods` folder inside your game folder
|
|
||||||
|
|
||||||
3\. You should end up with:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
Mods\RifleScope.dll
|
||||||
Mods\\RifleScope.dll
|
Mods\RifleScope\manifest.json
|
||||||
|
|
||||||
Mods\\RifleScope\\manifest.json
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
\## Usage
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Equip the rifle and aim. Once zoomed in, the rifle model is hidden and the scope
|
Equip the rifle and aim. Once zoomed in, the rifle model is hidden and the scope
|
||||||
|
|
||||||
view is shown. Your shot goes where the crosshair is.
|
view is shown. Your shot goes where the crosshair is.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The red number at the bottom left of the scope is the range in meters to whatever
|
The red number at the bottom left of the scope is the range in meters to whatever
|
||||||
|
|
||||||
is under the crosshair.
|
is under the crosshair.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The reticle has 10 mil dots in each direction. At 5x each dot is 2 mils, which
|
The reticle has 10 mil dots in each direction. At 5x each dot is 2 mils, which
|
||||||
|
|
||||||
covers 20 cm at 100 m. At 10x and higher each dot is 1 mil. Use them to hold over
|
covers 20 cm at 100 m. At 10x and higher each dot is 1 mil. Use them to hold over
|
||||||
|
|
||||||
or to the side at long range.
|
or to the side at long range.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Press F1 to open the console, then use these commands:
|
Press F1 to open the console, then use these commands:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
| Command | Action |
|
| Command | Action |
|
||||||
|
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
| `scope` | Show whether the scope is on or off |
|
||||||
| `scope` | Show whether the mod is on or off |
|
| `scope off` | Go back to the vanilla scope. The suppressor is not affected |
|
||||||
|
| `scope on` | Turn the scope back on |
|
||||||
| `scope off` | Turn the mod off and go back to the vanilla scope |
|
|
||||||
|
|
||||||
| `scope on` | Turn the mod back on |
|
|
||||||
|
|
||||||
| `scopezoom` | Show the current zoom |
|
| `scopezoom` | Show the current zoom |
|
||||||
|
|
||||||
| `scopezoom 5` | Set the zoom as a multiple of the vanilla scope, 1 to 50. The default is 5 |
|
| `scopezoom 5` | Set the zoom as a multiple of the vanilla scope, 1 to 50. The default is 5 |
|
||||||
|
|
||||||
| `scopetrim` | Show the current vertical trim |
|
| `scopetrim` | Show the current vertical trim |
|
||||||
|
| `scopetrim 60` | Set the vertical trim, -200 to 200. Positive moves hits down, negative moves hits up. The default is 60 |
|
||||||
| `scopetrim 50` | Set the vertical trim, -200 to 200. Positive moves hits down, negative moves hits up. The default is 50 |
|
|
||||||
|
|
||||||
| `scopewind` | Show the current windage |
|
| `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 |
|
| `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 |
|
| `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
|
Trim and windage are angle adjustments, so a setting that is right at one range
|
||||||
|
|
||||||
stays right at every 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,
|
folder. Setting the zoom to 1 keeps vanilla zoom and the normal scope view,
|
||||||
|
|
||||||
without the red dot.
|
without the red dot.
|
||||||
|
|
||||||
|
## Building from source
|
||||||
|
|
||||||
\## Building from source
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Requires the .NET 8 SDK and RedLoader installed with its game assemblies
|
Requires the .NET 8 SDK and RedLoader installed with its game assemblies
|
||||||
|
|
||||||
generated.
|
generated.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
.\build.ps1 -Install
|
||||||
.\\build.ps1 -Install
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Use `-Package` to build `RifleScope.zip` for a release. Pass `-GameDir "path"` if
|
Use `-Package` to build `RifleScope.zip` for a release. Pass `-GameDir "path"` if
|
||||||
|
|
||||||
the game is not found automatically.
|
the game is not found automatically.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
Check `_RedLoader\Latest.log` in your game folder. RifleScope logs a line when it
|
||||||
\## Troubleshooting
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Check `\_RedLoader\\Latest.log` in your game folder. RifleScope logs a line when it
|
|
||||||
|
|
||||||
loads.
|
loads.
|
||||||
|
|
||||||
-811
@@ -1,811 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using RedLoader;
|
|
||||||
using RedLoader.Utils;
|
|
||||||
using Sons.FieldOfView;
|
|
||||||
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 = 50f;
|
|
||||||
private const float MaxTrimCm = 200f;
|
|
||||||
private const float MaxWindCm = 200f;
|
|
||||||
private const float TrimReferenceRange = 50f;
|
|
||||||
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<IntPtr, float> OriginalOffsets = new();
|
|
||||||
private static readonly Dictionary<int, Material> HiddenMaterials = new();
|
|
||||||
private static readonly HashSet<int> 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 readonly List<(Renderer Renderer, int Slot, Material Original)> RedDotSlots = new();
|
|
||||||
private static string _configPath;
|
|
||||||
|
|
||||||
private static RifleAnimatorController _rifle;
|
|
||||||
private static float _nextSearch;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (!_enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_rifle || !_rifle.gameObject.activeInHierarchy)
|
|
||||||
{
|
|
||||||
_rifle = null;
|
|
||||||
SetZoomed(false, null);
|
|
||||||
if (Time.unscaledTime < _nextSearch)
|
|
||||||
return;
|
|
||||||
_nextSearch = Time.unscaledTime + 0.5f;
|
|
||||||
if (!FindRifle())
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool FindRifle()
|
|
||||||
{
|
|
||||||
foreach (var rifle in UnityEngine.Object.FindObjectsOfType<RifleAnimatorController>())
|
|
||||||
{
|
|
||||||
if (!rifle || !rifle.gameObject.activeInHierarchy || !rifle.IsLocalPlayer())
|
|
||||||
continue;
|
|
||||||
_rifle = rifle;
|
|
||||||
_projectile = FindProjectileTransform(rifle);
|
|
||||||
if (!_overlay)
|
|
||||||
CreateOverlay();
|
|
||||||
HideRedDot(rifle);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Transform FindProjectileTransform(RangedWeaponController rifle)
|
|
||||||
{
|
|
||||||
var constraint = rifle._projectileTransformConstraint;
|
|
||||||
if (constraint)
|
|
||||||
return constraint.transform;
|
|
||||||
foreach (var t in rifle.GetComponentsInChildren<Transform>(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<Renderer>(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();
|
|
||||||
_rifle = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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>();
|
|
||||||
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<RectTransform>();
|
|
||||||
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<TextMeshProUGUI>();
|
|
||||||
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<TMP_FontAsset>())
|
|
||||||
{
|
|
||||||
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<RawImage>();
|
|
||||||
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("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";
|
|
||||||
}
|
|
||||||
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")}");
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
RLog.Error($"RifleScope could not write config: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<AssemblyName>RifleScope</AssemblyName>
|
<AssemblyName>RifleScope</AssemblyName>
|
||||||
<RootNamespace>RifleScope</RootNamespace>
|
<RootNamespace>RifleScope</RootNamespace>
|
||||||
<Version>1.0.1</Version>
|
<Version>1.1.1</Version>
|
||||||
<NoWarn>CS0436;CS1701;CS1702;MSB3277;MSB3106</NoWarn>
|
<NoWarn>CS0436;CS1701;CS1702;MSB3277;MSB3106</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
"id": "RifleScope",
|
"id": "RifleScope",
|
||||||
"name": "RifleScope",
|
"name": "RifleScope",
|
||||||
"author": "JustinOros",
|
"author": "JustinOros",
|
||||||
"version": "1.0.1",
|
"version": "1.1.1",
|
||||||
"description": "Stronger rifle scope magnification and a crosshair reticle",
|
"description": "Stronger rifle scope magnification and a crosshair reticle",
|
||||||
"gameVersion": "1.0.0",
|
"gameVersion": "1.0.0",
|
||||||
"platform": "Client",
|
"platform": "Client",
|
||||||
|
|||||||
Reference in new issue
Block a user