WIP 1.2.0 - untested

This commit is contained in:
Terkoiz 2022-06-03 14:28:35 +03:00
parent d28425626d
commit b6092fee94
5 changed files with 281 additions and 62 deletions

View File

@ -1,4 +1,5 @@
using UnityEngine;
using JetBrains.Annotations;
using UnityEngine;
namespace Terkoiz.Freecam
{
@ -13,7 +14,8 @@ namespace Terkoiz.Freecam
{
public bool IsActive = false;
void Update()
[UsedImplicitly]
public void Update()
{
if (!IsActive)
{
@ -43,6 +45,8 @@ namespace Terkoiz.Freecam
transform.position += (-transform.forward * movementSpeed * Time.deltaTime);
}
if (FreecamPlugin.CameraHeightMovement.Value)
{
if (Input.GetKey(KeyCode.Q))
{
transform.position += (transform.up * movementSpeed * Time.deltaTime);
@ -62,11 +66,14 @@ namespace Terkoiz.Freecam
{
transform.position += (-Vector3.up * movementSpeed * Time.deltaTime);
}
}
float newRotationX = transform.localEulerAngles.y + Input.GetAxis("Mouse X") * FreecamPlugin.CameraLookSensitivity.Value;
float newRotationY = transform.localEulerAngles.x - Input.GetAxis("Mouse Y") * FreecamPlugin.CameraLookSensitivity.Value;
transform.localEulerAngles = new Vector3(newRotationY, newRotationX, 0f);
if (FreecamPlugin.CameraMousewheelZoom.Value)
{
float axis = Input.GetAxis("Mouse ScrollWheel");
if (axis != 0)
{
@ -76,3 +83,4 @@ namespace Terkoiz.Freecam
}
}
}
}

View File

@ -1,23 +1,55 @@
using Comfort.Common;
using System.Reflection;
using Comfort.Common;
using EFT;
using EFT.CameraControl;
using EFT.UI;
using HarmonyLib;
using JetBrains.Annotations;
using MonoMod.RuntimeDetour;
using UnityEngine;
namespace Terkoiz.Freecam
{
public class FreecamController : MonoBehaviour
{
private GameObject mainCamera;
private Freecam freeCamScript;
private GameObject _mainCamera;
private Freecam _freeCamScript;
private BattleUIScreen playerUi;
private bool uiHidden;
private BattleUIScreen _playerUi;
private bool _uiHidden;
// TODO MAYBE:
// Hide player weapon
private MethodInfo _playerMoveMethod;
private MethodInfo _playerRotateMethod;
private Detour _moveDetour;
private Detour _rotateDetour;
private Vector3? _lastPosition;
private Quaternion? _lastRotation;
// TODO:
// Hide version number UI element
// FreeCam controller support (camera could be smoother with an analog stick, apparently)
// Button to toggle between camera and player movement
// Independent FoV setting for Freecam mode (_mainCamera.GetComponent<Camera>().fieldOfView = ...)
[UsedImplicitly]
public void Start()
{
// We locate and get the MethodInfo for the Move method that we will later detour to prevent the player from moving during Freecam
_playerMoveMethod = typeof(Player).GetMethod("Move");
if (_playerMoveMethod == null)
{
FreecamPlugin.Logger.LogError("Failed to locate the Player.Move method!");
}
// Same deal here, but for player rotation
_playerRotateMethod = typeof(Player).GetMethod("Rotate");
if (_playerRotateMethod == null)
{
FreecamPlugin.Logger.LogError("Failed to locate the Player.Rotate method!");
}
}
[UsedImplicitly]
public void Update()
{
if (FreecamPlugin.ToggleUi.Value.IsDown())
@ -49,36 +81,33 @@ namespace Terkoiz.Freecam
}
// If we don't have the main camera object cached, go look for it in the scene
if (mainCamera == null)
if (_mainCamera == null)
{
// Finding a GameObject by name directly is apparantly better than searching for objects of a type.
// Finding a GameObject by name directly is apparently better than searching for objects of a type.
// 'FPS Camera' is our main camera instance - so we just grab that
mainCamera = GameObject.Find("FPS Camera");
if (mainCamera == null)
_mainCamera = GameObject.Find("FPS Camera");
if (_mainCamera == null)
{
Debug.LogError("Terkoiz.Freecam: ERROR: Failed to locate main camera");
FreecamPlugin.Logger.LogError("Failed to locate main camera");
PreloaderUI.Instance.Console.AddLog("ERROR: Failed to locate main camera", "FreeCam");
return;
}
}
// Create the Freecam script and attach it to the main camera
if (freeCamScript == null)
if (_freeCamScript == null)
{
freeCamScript = mainCamera.AddComponent<Freecam>();
ClearDetours();
_freeCamScript = _mainCamera.AddComponent<Freecam>();
}
// We disable the player's GameObject, which prevents the player from moving and interacting with the world while we're in Freecam mode
// Also, since the camera position keeps getting updated to where the player is, disabling the player's GameObject also stops this behaviour
if (!freeCamScript.IsActive)
if (!_freeCamScript.IsActive)
{
localPlayer.gameObject.SetActive(false);
freeCamScript.IsActive = true;
SetPlayerToFreecamMode(localPlayer);
}
else
{
freeCamScript.IsActive = false;
localPlayer.gameObject.SetActive(true);
SetPlayerToFirstPersonMode(localPlayer);
}
}
@ -94,19 +123,19 @@ namespace Terkoiz.Freecam
}
// If we don't have the main camera cached, it means we have yet to enter Freecam mode and there is nowhere to teleport to
if (mainCamera == null)
if (_mainCamera == null)
{
return;
}
// We basically do what ToggleCamera() does, but with extra code inbetween
if (freeCamScript.IsActive)
// Move the player to the camera's current position and switch to First Person mode
if (_freeCamScript.IsActive)
{
freeCamScript.IsActive = false;
// We grab the camera's position, but we subtract a bit off the Y axis, because the players coordinate origin is at the feet
var position = new Vector3(mainCamera.transform.position.x, mainCamera.transform.position.y - 1.8f, mainCamera.transform.position.z);
localPlayer.gameObject.transform.SetPositionAndRotation(position, mainCamera.transform.rotation);
localPlayer.gameObject.SetActive(true);
var position = new Vector3(_mainCamera.transform.position.x, _mainCamera.transform.position.y - 1.8f, _mainCamera.transform.position.z);
localPlayer.gameObject.transform.SetPositionAndRotation(position, Quaternion.Euler(0, _mainCamera.transform.rotation.y, 0));
// localPlayer.gameObject.transform.SetPositionAndRotation(position, _mainCamera.transform.rotation);
SetPlayerToFirstPersonMode(localPlayer);
}
}
@ -122,26 +151,97 @@ namespace Terkoiz.Freecam
}
// If we don't have the UI Component cached, go look for it in the scene
if (playerUi == null)
if (_playerUi == null)
{
playerUi = GameObject.Find("BattleUIScreen").GetComponent<BattleUIScreen>();
_playerUi = GameObject.Find("BattleUIScreen").GetComponent<BattleUIScreen>();
if (playerUi == null)
if (_playerUi == null)
{
FreecamPlugin.Logger.LogError("Failed to locate player UI");
PreloaderUI.Instance.Console.AddLog("ERROR: Failed to locate player UI", "FreeCam");
return;
}
}
playerUi.gameObject.SetActive(uiHidden);
uiHidden = !uiHidden;
_playerUi.gameObject.SetActive(_uiHidden);
_uiHidden = !_uiHidden;
}
/// <summary>
/// A helper method to set the Player into Freecam mode
/// </summary>
/// <param name="localPlayer"></param>
private void SetPlayerToFreecamMode(Player localPlayer)
{
// We set the player to third person mode, but then we want set the camera to freecam mode
// This means our character will be fully visible, while letting the camera move freely
// Setting the player point of view directly to Freecam seems to hide the head and arms of the character, which is not desirable
localPlayer.PointOfView = EPointOfView.ThirdPerson;
// Get the PlayerBody reference. It's a protected field, so we have to use traverse to fetch it
var playerBody = Traverse.Create(localPlayer).Field<PlayerBody>("_playerBody").Value;
if (playerBody != null)
{
// We tell the PlayerBody class that it's in FreeCamera mode, and force an update of the Camera Controller view mode
// Setting the PointOfView.Value directly skips all the code that would usually change how the player body is rendered
playerBody.PointOfView.Value = EPointOfView.FreeCamera;
localPlayer.GetComponent<PlayerCameraController>().UpdatePointOfView();
// All we really needed to do, was trigger the UpdatePointOfView method and have it update to the FreeCam state
// There's no easy way of doing this without patching the method, and even then it might be a bloated solution
}
else
{
FreecamPlugin.Logger.LogError("Failed to get the PlayerBody field");
PreloaderUI.Instance.Console.AddLog("ERROR: Failed to get the PlayerBody field", "FreeCam");
}
// Detour the Player.Move method into an empty one, preventing the character from moving during Freecam mode
if (_playerMoveMethod != null)
{
_moveDetour = new Detour(_playerMoveMethod, typeof(FreecamController).GetMethod(nameof(DisabledMove)));
}
// Same deal here, but for player rotation
if (_playerRotateMethod != null)
{
_rotateDetour = new Detour(_playerRotateMethod, typeof(FreecamController).GetMethod(nameof(DisabledRotate)));
}
if (FreecamPlugin.CameraRememberLastPosition.Value && _lastPosition != null && _lastRotation != null)
{
_mainCamera.transform.position = _lastPosition.Value;
_mainCamera.transform.rotation = _lastRotation.Value;
}
_freeCamScript.IsActive = true;
}
/// <summary>
/// A helper method to reset the player view back to First Person
/// </summary>
/// <param name="localPlayer"></param>
private void SetPlayerToFirstPersonMode(Player localPlayer)
{
_freeCamScript.IsActive = false;
if (FreecamPlugin.CameraRememberLastPosition.Value)
{
_lastPosition = _mainCamera.transform.position;
_lastRotation = _mainCamera.transform.rotation;
}
ClearDetours();
localPlayer.PointOfView = EPointOfView.FirstPerson;
}
/// <summary>
/// Gets the current <see cref="Player"/> instance if it's available
/// </summary>
/// <returns>Local <see cref="Player"/> instance; returns null if the game is not in raid</returns>
private static Player GetLocalPlayerFromWorld()
private Player GetLocalPlayerFromWorld()
{
// If the GameWorld instance is null or has no RegisteredPlayers, it most likely means we're not in a raid
var gameWorld = Singleton<GameWorld>.Instance;
@ -150,8 +250,51 @@ namespace Terkoiz.Freecam
return null;
}
// One of the RegisterdPlayers will have the IsYourPlayer flag set, which will be our own Player instance
// One of the RegisteredPlayers will have the IsYourPlayer flag set, which will be our own Player instance
return gameWorld.RegisteredPlayers.Find(p => p.IsYourPlayer);
}
[UsedImplicitly]
public void OnDestroy()
{
_freeCamScript.IsActive = false;
// Making sure no stray Detours are left
ClearDetours();
_lastPosition = null;
_lastRotation = null;
}
public void ClearDetours()
{
if (_moveDetour != null)
{
_moveDetour.Dispose();
_moveDetour = null;
}
if (_rotateDetour != null)
{
_rotateDetour.Dispose();
_rotateDetour = null;
}
}
/// <summary>
/// An empty method that's used to detour the player Move method, preventing movement during Freecam mode
/// </summary>
[UsedImplicitly]
public static void DisabledMove(Player self, Vector2 direction)
{
}
/// <summary>
/// An empty method that's used to detour the player Rotate method, preventing rotation during Freecam mode
/// </summary>
[UsedImplicitly]
public static void DisabledRotate(Player self, Vector2 deltaRotation, bool ignoreClamp = false)
{
}
}
}

View File

@ -1,12 +1,16 @@
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using JetBrains.Annotations;
using UnityEngine;
namespace Terkoiz.Freecam
{
[BepInPlugin("com.terkoiz.freecam", "Terkoiz.Freecam", "1.1.1")]
[BepInPlugin("com.terkoiz.freecam", "Terkoiz.Freecam", "1.2.0")]
public class FreecamPlugin : BaseUnityPlugin
{
internal new static ManualLogSource Logger { get; private set; }
private static GameObject HookObject;
// Keyboard shortcut config entries
@ -23,13 +27,23 @@ namespace Terkoiz.Freecam
internal static ConfigEntry<float> CameraZoomSpeed;
internal static ConfigEntry<float> CameraFastZoomSpeed;
private void Awake()
private const string TogglesSectionName = "Toggles";
internal static ConfigEntry<bool> CameraHeightMovement;
internal static ConfigEntry<bool> CameraMousewheelZoom;
internal static ConfigEntry<bool> CameraRememberLastPosition;
// TODO: Hook into camera OnDestroy to run the OnDestroy from FreecamController
[UsedImplicitly]
internal void Start()
{
Logger = base.Logger;
InitConfiguration();
HookObject = new GameObject();
HookObject.AddComponent<FreecamController>();
Object.DontDestroyOnLoad(HookObject);
DontDestroyOnLoad(HookObject);
}
private void InitConfiguration()
@ -91,6 +105,25 @@ namespace Terkoiz.Freecam
new ConfigDescription(
"Amount to zoom the camera when using the mouse wheel while holding Shift",
new AcceptableValueRange<float>(0.01f, 1000f)));
CameraHeightMovement = Config.Bind(
TogglesSectionName,
"CameraHeightMovementKeys",
true,
"Enables or disables the camera height movement keys, which default to Q, E, R, F." +
" \nUseful to disable if you want to let your character lean in Freecam mode");
CameraMousewheelZoom = Config.Bind(
TogglesSectionName,
"CameraMousewheelZoom",
true,
"Enables or disables camera movement on mousewheel scroll. Just in case you find it annoying.");
CameraRememberLastPosition = Config.Bind(
TogglesSectionName,
"CameraRememberLastPosition",
false,
"If enabled, returning to Freecam mode will put the camera to it's last position which was saved when exiting Freecam mode.");
}
}
}

View File

@ -13,6 +13,8 @@
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -39,11 +41,14 @@
</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony, Version=2.7.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\HarmonyX.2.7.0\lib\net45\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\Shared\Hollowed\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="BepInEx, Version=5.4.17.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\BepInEx.BaseLib.5.4.17\lib\net35\BepInEx.dll</HintPath>
<Reference Include="BepInEx, Version=5.4.19.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\BepInEx.BaseLib.5.4.19\lib\net35\BepInEx.dll</HintPath>
</Reference>
<Reference Include="Comfort">
<HintPath>..\Shared\Managed\Comfort.dll</HintPath>
@ -51,6 +56,24 @@
<Reference Include="ItemComponent.Types">
<HintPath>..\Shared\Managed\ItemComponent.Types.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil, Version=0.11.4.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.11.4\lib\net40\Mono.Cecil.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Mdb, Version=0.11.4.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.11.4\lib\net40\Mono.Cecil.Mdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Pdb, Version=0.11.4.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.11.4\lib\net40\Mono.Cecil.Pdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Rocks, Version=0.11.4.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.11.4\lib\net40\Mono.Cecil.Rocks.dll</HintPath>
</Reference>
<Reference Include="MonoMod.RuntimeDetour, Version=21.12.13.1, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MonoMod.RuntimeDetour.21.12.13.1\lib\net452\MonoMod.RuntimeDetour.dll</HintPath>
</Reference>
<Reference Include="MonoMod.Utils, Version=21.12.13.1, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MonoMod.Utils.21.12.13.1\lib\net452\MonoMod.Utils.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="UnityEngine">
<HintPath>..\Shared\Managed\UnityEngine.dll</HintPath>
@ -76,4 +99,11 @@
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\BepInEx.Core.5.4.19\build\BepInEx.Core.targets" Condition="Exists('..\packages\BepInEx.Core.5.4.19\build\BepInEx.Core.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\BepInEx.Core.5.4.19\build\BepInEx.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\BepInEx.Core.5.4.19\build\BepInEx.Core.targets'))" />
</Target>
</Project>

View File

@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BepInEx.BaseLib" version="5.4.17" targetFramework="net472" />
<package id="BepInEx.BaseLib" version="5.4.19" targetFramework="net472" />
<package id="BepInEx.Core" version="5.4.19" targetFramework="net472" />
<package id="HarmonyX" version="2.7.0" targetFramework="net472" />
<package id="Mono.Cecil" version="0.11.4" targetFramework="net472" />
<package id="MonoMod.RuntimeDetour" version="21.12.13.1" targetFramework="net472" />
<package id="MonoMod.Utils" version="21.12.13.1" targetFramework="net472" />
</packages>