This repository has been archived on 2024-12-03. You can view files and clone it, but cannot push or open issues or pull requests.
Freecam/project/Terkoiz.Freecam/FreecamController.cs
GrooveypenguinX 027893c1d6 Freecam Controls Toggle (#4)
Added option to toggle Freecam Controls using a new keybind (default: keypad period).

This allows users to easily switch to Freecam mode, move their camera to position, toggle camera controls, and move the player character. Great addition for efficient testing of weight painting and animations.

Reviewed-on: #4
Co-authored-by: GrooveypenguinX <grooveypenguinx@noreply.dev.sp-tarkov.com>
Co-committed-by: GrooveypenguinX <grooveypenguinx@noreply.dev.sp-tarkov.com>
2024-03-03 13:40:33 +00:00

246 lines
8.8 KiB
C#

using Comfort.Common;
using EFT;
using EFT.CameraControl;
using EFT.UI;
using HarmonyLib;
using JetBrains.Annotations;
using UnityEngine;
namespace Terkoiz.Freecam
{
public class FreecamController : MonoBehaviour
{
private GameObject _mainCamera;
private Freecam _freeCamScript;
private BattleUIScreen _playerUi;
private bool _uiHidden;
private GamePlayerOwner _gamePlayerOwner;
private Vector3? _lastPosition;
private Quaternion? _lastRotation;
[UsedImplicitly]
public void Start()
{
// Find Main Camera
_mainCamera = GameObject.Find("FPS Camera");
if (_mainCamera == null)
{
FreecamPlugin.Logger.LogError("Failed to locate main camera");
return;
}
// Add Freecam script to main camera in scene
_freeCamScript = _mainCamera.AddComponent<Freecam>();
if (_freeCamScript == null)
{
FreecamPlugin.Logger.LogError("Failed to add Freecam script to Camera");
}
// Get GamePlayerOwner component
_gamePlayerOwner = GetLocalPlayerFromWorld().GetComponentInChildren<GamePlayerOwner>();
if (_gamePlayerOwner == null)
{
FreecamPlugin.Logger.LogError("Failed to locate GamePlayerOwner");
}
}
[UsedImplicitly]
public void Update()
{
if (FreecamPlugin.ToggleUi.Value.IsDown())
{
ToggleUi();
}
if (FreecamPlugin.ToggleFreecamMode.Value.IsDown())
{
ToggleCamera();
}
if (FreecamPlugin.ToggleFreecamControls.Value.IsDown())
{
ToggleCameraControls();
}
if (FreecamPlugin.TeleportToCamera.Value.IsDown())
{
MovePlayerToCamera();
}
}
/// <summary>
/// Toggles the Freecam mode
/// </summary>
private void ToggleCamera()
{
// Get our own Player instance. Null means we're not in a raid
var localPlayer = GetLocalPlayerFromWorld();
if (localPlayer == null)
return;
if (!_freeCamScript.IsActive)
{
SetPlayerToFreecamMode(localPlayer);
}
else
{
SetPlayerToFirstPersonMode(localPlayer);
}
}
/// <summary>
/// When triggered during Freecam mode, teleports the player to where the camera was and exits Freecam mode
/// </summary>
private void MovePlayerToCamera()
{
var localPlayer = GetLocalPlayerFromWorld();
if (localPlayer == null)
return;
// Move the player to the camera's current position and switch to First Person mode
if (_freeCamScript.IsActive)
{
// Tell the fall damage patch that we just teleported. Used for the "smart" fall damage prevention feature
FallDamagePatch.HasTeleported = true;
// 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, Quaternion.Euler(0, _mainCamera.transform.rotation.y, 0));
// localPlayer.gameObject.transform.SetPositionAndRotation(position, _mainCamera.transform.rotation);
SetPlayerToFirstPersonMode(localPlayer);
}
}
/// <summary>
/// Hides the main UI (health, stamina, stance, hotbar, etc.)
/// </summary>
private void ToggleUi()
{
// Check if we're currently in a raid
if (GetLocalPlayerFromWorld() == null)
return;
// If we don't have the UI Component cached, go look for it in the scene
if (_playerUi == null)
{
_playerUi = GameObject.Find("BattleUIScreen").GetComponent<BattleUIScreen>();
if (_playerUi == null)
{
FreecamPlugin.Logger.LogError("Failed to locate player UI");
return;
}
}
_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");
}
// Instead of Detouring, just turn off _gamePlayerOwner which takes the input
_gamePlayerOwner.enabled = false;
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;
}
// re-enable _gamePlayerOwner
_gamePlayerOwner.enabled = true;
localPlayer.PointOfView = EPointOfView.FirstPerson;
}
/// <summary>
/// A helper method to toggle the Freecam Camera Controls
/// </summary>
private void ToggleCameraControls()
{
if (_freeCamScript.IsActive)
{
_freeCamScript.IsActive = false;
_gamePlayerOwner.enabled = true;
}
else
{
_freeCamScript.IsActive = true;
_gamePlayerOwner.enabled = false;
}
}
/// <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 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;
if (gameWorld == null || gameWorld.MainPlayer == null)
return null;
// One of the RegisteredPlayers will have the IsYourPlayer flag set, which will be our own Player instance
return gameWorld.MainPlayer;
}
[UsedImplicitly]
public void OnDestroy()
{
// Destroy FreeCamScript before FreeCamController if exists
Destroy(_freeCamScript);
Destroy(this);
}
}
}