diff --git a/Source/Configuration/PluginConfigSettings.cs b/Source/Configuration/PluginConfigSettings.cs index c92b2ecbd..3cc4c2c1e 100644 --- a/Source/Configuration/PluginConfigSettings.cs +++ b/Source/Configuration/PluginConfigSettings.cs @@ -121,12 +121,12 @@ public bool SETTING_DEBUGShowPlayerList public int WaitingTimeBeforeStart { get; private set; } - public int BlackScreenOnDeathTime + public float BlackScreenOnDeathTime { get { return StayInTarkovPlugin.Instance.Config.Bind - ("Coop", "BlackScreenOnDeathTime", 500, new ConfigDescription("How long to wait until your death waits to become a Free Camera")).Value; + ("Coop", "BlackScreenOnDeathTime", 5F, new ConfigDescription("How long to wait after death until you become a Free Camera")).Value; } } diff --git a/Source/Coop/FreeCamera/FreeCamera.cs b/Source/Coop/FreeCamera/FreeCamera.cs index f516ec0ea..8c32df980 100644 --- a/Source/Coop/FreeCamera/FreeCamera.cs +++ b/Source/Coop/FreeCamera/FreeCamera.cs @@ -1,4 +1,9 @@ -using JetBrains.Annotations; +#nullable enable + +using StayInTarkov.Coop.Components.CoopGameComponents; +using StayInTarkov.Coop.Players; +using System.Collections.Generic; +using System.Linq; using UnityEngine; namespace StayInTarkov.Coop.FreeCamera @@ -10,85 +15,216 @@ namespace StayInTarkov.Coop.FreeCamera /// https://gist.github.com/ashleydavis/f025c03a9221bc840a2b /// /// This is HEAVILY based on Terkoiz's work found here. Thanks for your work Terkoiz! - /// https://dev.sp-tarkov.com/Terkoiz/Freecam/raw/branch/master/project/Terkoiz.Freecam/FreecamController.cs + /// https://dev.sp-tarkov.com/Terkoiz/Freecam/raw/branch/master/project/Terkoiz.Freecam/Freecam.cs /// public class FreeCamera : MonoBehaviour { - public bool IsActive = false; + private CoopPlayer? _playerSpectating; + private bool _isSpectatingPlayer = false; + private bool _spectateRightShoulder = true; + + public bool IsActive { get; set; } = false; - [UsedImplicitly] - public void Update() + private void StopSpectatingPlayer() { - if (!IsActive) + if (_playerSpectating != null) { - return; + _playerSpectating = null; + } + if (transform.parent != null) + { + transform.parent = null; + } + _isSpectatingPlayer = false; + } + + private void SpectateNextPlayer() + { + UpdatePlayerSpectator(true); + } + + private void SpectatePreviousPlayer() + { + UpdatePlayerSpectator(false); + } + + /// + /// Updates the player beign followed by the camera + /// + /// True for the next player and false for the previous player + private void UpdatePlayerSpectator(bool nextPlayer) + { + SITGameComponent coopGameComponent = SITGameComponent.GetCoopGameComponent(); + List players = [.. coopGameComponent + .Players + .Values + .Where(x => !x.IsYourPlayer && x.HealthController.IsAlive && x.GroupId?.Contains("SIT") == true) + ]; + + if (players.Count > 0) + { + if (_playerSpectating == null) + { + if (players[0] != null) + { + _playerSpectating = players[0]; + } + } + else + { + int playerIndex = 0; + if (nextPlayer) + { + // We want to look for the next player in the list + playerIndex = players.IndexOf(_playerSpectating) + 1; + if (playerIndex > players.Count - 1) + { + playerIndex = 0; + } + } + else + { + // We want to find the previous player + playerIndex = players.IndexOf(_playerSpectating) - 1; + if (playerIndex < 0) + { + playerIndex = players.Count - 1; + } + } + + // Update the player we are spectating + _playerSpectating = players[playerIndex]; + } + + if (_playerSpectating != null) + { + _isSpectatingPlayer = true; + + // Attach the camera to the player we are spectating; + SetPlayerSpectateShoulder(); + } + } + else + { + StopSpectatingPlayer(); } + } - var fastMode = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); - var movementSpeed = fastMode ? 20f : 3f; + private void MoveAndRotateCamera() + { + bool fastMode = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + float movementSpeed = fastMode ? 20f : 3f; + // Strafe Right if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) { transform.position += (-transform.right * (movementSpeed * Time.deltaTime)); } + // Strafe Left if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) { transform.position += (transform.right * (movementSpeed * Time.deltaTime)); } + // Forwards if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) { transform.position += (transform.forward * (movementSpeed * Time.deltaTime)); } + // Backwards if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) { transform.position += (-transform.forward * (movementSpeed * Time.deltaTime)); } - if (true) + // Up + if (Input.GetKey(KeyCode.Q)) { - if (Input.GetKey(KeyCode.Q)) - { - transform.position += (transform.up * (movementSpeed * Time.deltaTime)); - } + transform.position += (transform.up * (movementSpeed * Time.deltaTime)); + } - if (Input.GetKey(KeyCode.E)) - { - transform.position += (-transform.up * (movementSpeed * Time.deltaTime)); - } + // Down + if (Input.GetKey(KeyCode.E)) + { + transform.position += (-transform.up * (movementSpeed * Time.deltaTime)); + } - if (Input.GetKey(KeyCode.R) || Input.GetKey(KeyCode.PageUp)) - { - transform.position += (Vector3.up * (movementSpeed * Time.deltaTime)); - } + // Up + if (Input.GetKey(KeyCode.R) || Input.GetKey(KeyCode.PageUp)) + { + transform.position += (Vector3.up * (movementSpeed * Time.deltaTime)); + } - if (Input.GetKey(KeyCode.F) || Input.GetKey(KeyCode.PageDown)) - { - transform.position += (-Vector3.up * (movementSpeed * Time.deltaTime)); - } + // Down + if (Input.GetKey(KeyCode.F) || Input.GetKey(KeyCode.PageDown)) + { + transform.position += (-Vector3.up * (movementSpeed * Time.deltaTime)); } float newRotationX = transform.localEulerAngles.y + Input.GetAxis("Mouse X") * 3f; float newRotationY = transform.localEulerAngles.x - Input.GetAxis("Mouse Y") * 3f; transform.localEulerAngles = new Vector3(newRotationY, newRotationX, 0f); + } - //if (FreecamPlugin.CameraMousewheelZoom.Value) - //{ - // float axis = Input.GetAxis("Mouse ScrollWheel"); - // if (axis != 0) - // { - // var zoomSensitivity = fastMode ? FreecamPlugin.CameraFastZoomSpeed.Value : FreecamPlugin.CameraZoomSpeed.Value; - // transform.position += transform.forward * (axis * zoomSensitivity); - // } - //} + private void SetPlayerSpectateShoulder() + { + if (_isSpectatingPlayer) + { + if (_spectateRightShoulder) + { + transform.parent = _playerSpectating?.PlayerBones.RightShoulder.Original; + transform.localEulerAngles = new Vector3(250, 270, 270); + transform.localPosition = new Vector3(-0.12f, 0.04f, 0.16f); + } + else + { + transform.parent = _playerSpectating?.PlayerBones.LeftShoulder.Original; + transform.localEulerAngles = new Vector3(250, 90, 270); + transform.localPosition = new Vector3(-0.12f, -0.04f, -0.16f); + } + } } - [UsedImplicitly] - private void OnDestroy() + protected void OnDestroy() { Destroy(this); } + + protected void Update() + { + if (!IsActive) + { + return; + } + + // Spectate the next player + if (Input.GetKeyDown(KeyCode.Mouse0)) + { + SpectateNextPlayer(); + } + // Spectate the previous player + else if (Input.GetKeyDown(KeyCode.Mouse1)) + { + SpectatePreviousPlayer(); + } + // Stop following the currently selected player + else if (Input.GetKeyDown(KeyCode.End)) + { + StopSpectatingPlayer(); + } + else if (Input.GetKeyDown(KeyCode.Home)) + { + _spectateRightShoulder = !_spectateRightShoulder; + SetPlayerSpectateShoulder(); + } + + // If we aren't spectating anyone then just update the camera normally + if (!_isSpectatingPlayer) + { + MoveAndRotateCamera(); + } + } } } \ No newline at end of file diff --git a/Source/Coop/FreeCamera/FreeCameraController.cs b/Source/Coop/FreeCamera/FreeCameraController.cs index 71676ec26..eaac61653 100644 --- a/Source/Coop/FreeCamera/FreeCameraController.cs +++ b/Source/Coop/FreeCamera/FreeCameraController.cs @@ -1,13 +1,17 @@ -using BSG.CameraEffects; +#nullable enable + +using BepInEx.Logging; +using BSG.CameraEffects; using Comfort.Common; using EFT; using EFT.CameraControl; using EFT.UI; -using HarmonyLib; using StayInTarkov.Configuration; using StayInTarkov.Coop.Components.CoopGameComponents; +using StayInTarkov.Coop.Players; using StayInTarkov.Coop.SITGameModes; using System; +using System.Collections; using UnityEngine; using UnityStandardAssets.ImageEffects; @@ -20,22 +24,25 @@ namespace StayInTarkov.Coop.FreeCamera public class FreeCameraController : MonoBehaviour { - //private GameObject _mainCamera; - private FreeCamera _freeCamScript; + private FreeCamera? _freeCamScript; - private BattleUIScreen _playerUi; + private BattleUIScreen? _playerUi; private bool _uiHidden; - private GamePlayerOwner _gamePlayerOwner; + private GamePlayerOwner? _gamePlayerOwner; + private DateTime _lastTime = DateTime.MinValue; + + private ManualLogSource Logger { get; } = BepInEx.Logging.Logger.CreateLogSource("FreeCameraController"); + private CoopPlayer Player => (CoopPlayer) Singleton.Instance.MainPlayer; - public GameObject CameraParent { get; set; } - public Camera CameraFreeCamera { get; private set; } - public Camera CameraMain { get; private set; } + public GameObject? CameraParent { get; set; } + public Camera? CameraFreeCamera { get; private set; } + public Camera? CameraMain { get; private set; } - void Awake() + protected void Awake() { CameraParent = new GameObject("CameraParent"); - var FCamera = CameraParent.GetOrAddComponent(); + Camera FCamera = CameraParent.GetOrAddComponent(); FCamera.enabled = false; } @@ -56,129 +63,119 @@ public void Start() } // Get GamePlayerOwner component - _gamePlayerOwner = GetLocalPlayerFromWorld().GetComponentInChildren(); + _gamePlayerOwner = GetLocalPlayerFromWorld()?.GetComponentInChildren(); if (_gamePlayerOwner == null) { return; } + + Player.OnPlayerDead += Player_OnPlayerDead; } - private DateTime _lastTime = DateTime.MinValue; + private IEnumerator PlayerDeathRoutine() + { + yield return new WaitForSeconds(PluginConfigSettings.Instance?.CoopSettings.BlackScreenOnDeathTime ?? 5); - int DeadTime = 0; + var fpsCamInstance = CameraClass.Instance; + if (fpsCamInstance == null) + { + Logger.LogDebug("fpsCamInstance for camera is null"); + yield break; + } + + // Reset FOV after died + if (fpsCamInstance.Camera != null) + fpsCamInstance.Camera.fieldOfView = Singleton.Instance.Game.Settings.FieldOfView; + + EffectsController effectsController = fpsCamInstance.EffectsController; + if (effectsController == null) + { + Logger.LogDebug("effects controller for camera is null"); + yield break; + } + + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + //DisableAndDestroyEffect(effectsController.GetComponent()); + + var ccBlends = fpsCamInstance.EffectsController.GetComponents(); + if (ccBlends != null) + foreach (var ccBlend in ccBlends) + DisableAndDestroyEffect(ccBlend); + + DisableAndDestroyEffect(fpsCamInstance.VisorEffect); + DisableAndDestroyEffect(fpsCamInstance.NightVision); + DisableAndDestroyEffect(fpsCamInstance.ThermalVision); + + // Go to free camera mode + ToggleCamera(); + ToggleUi(); + } + + private void Player_OnPlayerDead(EFT.Player player, IPlayer lastAggressor, DamageInfo damageInfo, EBodyPart part) + { + Player.OnPlayerDead -= Player_OnPlayerDead; + StartCoroutine(PlayerDeathRoutine()); + } public void Update() { if (_gamePlayerOwner == null) return; - if (_gamePlayerOwner.Player == null) + if (Player == null) return; - if (_gamePlayerOwner.Player.PlayerHealthController == null) + if (Player.PlayerHealthController == null) return; - if (!SITGameComponent.TryGetCoopGameComponent(out var coopGC)) + if (!SITGameComponent.TryGetCoopGameComponent(out SITGameComponent coopGC)) return; - var coopGame = coopGC.LocalGameInstance as CoopSITGame; + CoopSITGame coopGame = (CoopSITGame) coopGC.LocalGameInstance; if (coopGame == null) return; var quitState = coopGC.GetQuitState(); - - if (_gamePlayerOwner.Player.PlayerHealthController.IsAlive - && (Input.GetKey(KeyCode.F9) || (quitState != SITGameComponent.EQuitState.NONE && !_freeCamScript.IsActive)) - && _lastTime < DateTime.Now.AddSeconds(-3)) + if (Player.PlayerHealthController.IsAlive && + (Input.GetKey(KeyCode.F9) || (quitState != SITGameComponent.EQuitState.NONE && _freeCamScript?.IsActive == false)) && + _lastTime < DateTime.Now.AddSeconds(-3)) { _lastTime = DateTime.Now; ToggleCamera(); ToggleUi(); - } - - if (!_gamePlayerOwner.Player.PlayerHealthController.IsAlive) - { - // This is to make sure the screen effect remove code only get executed once, instead of running every frame. - if (DeadTime == -1) - return; - - if (DeadTime < PluginConfigSettings.Instance.CoopSettings.BlackScreenOnDeathTime) - { - DeadTime++; - } - else - { - DeadTime = -1; - - var fpsCamInstance = CameraClass.Instance; - if (fpsCamInstance == null) - return; - - // Reset FOV after died - if (fpsCamInstance.Camera != null) - fpsCamInstance.Camera.fieldOfView = Singleton.Instance.Game.Settings.FieldOfView; - - var effectsController = fpsCamInstance.EffectsController; - if (effectsController == null) - return; - - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - //DisableAndDestroyEffect(effectsController.GetComponent()); - - var ccBlends = fpsCamInstance.EffectsController.GetComponents(); - if (ccBlends != null) - foreach (var ccBlend in ccBlends) - DisableAndDestroyEffect(ccBlend); - - DisableAndDestroyEffect(fpsCamInstance.VisorEffect); - DisableAndDestroyEffect(fpsCamInstance.NightVision); - DisableAndDestroyEffect(fpsCamInstance.ThermalVision); - - // Go to free camera mode - ToggleCamera(); - ToggleUi(); - } - } + } } - //DateTime? _lastOcclusionCullCheck = null; - //Vector3? _playerDeathOrExitPosition; - //bool showAtDeathOrExitPosition; - /// /// Toggles the Freecam mode /// public void ToggleCamera() { // Get our own Player instance. Null means we're not in a raid - var localPlayer = GetLocalPlayerFromWorld(); - if (localPlayer == null) + if (Player == null) return; - if (!_freeCamScript.IsActive) + if (_freeCamScript?.IsActive == false) { GameObject[] allGameObject = Resources.FindObjectsOfTypeAll(); foreach (GameObject gobj in allGameObject) { - if (gobj.GetComponent() != null) - { - gobj.GetComponent().ForceEnable(true); - } + gobj.GetComponent()?.ForceEnable(true); } - SetPlayerToFreecamMode(localPlayer); + SetPlayerToFreecamMode(Player); } else { - SetPlayerToFirstPersonMode(localPlayer); + SetPlayerToFirstPersonMode(Player); } } @@ -188,21 +185,20 @@ public void ToggleCamera() public void ToggleUi() { // Check if we're currently in a raid - if (GetLocalPlayerFromWorld() == null) + if (Player == null) return; // If we don't have the UI Component cached, go look for it in the scene if (_playerUi == null) { - var gameObject = GameObject.Find("BattleUIScreen"); + GameObject gameObject = GameObject.Find("BattleUIScreen"); if (gameObject == null) return; _playerUi = gameObject.GetComponent(); - if (_playerUi == null) { - //FreecamPlugin.Logger.LogError("Failed to locate player UI"); + Logger.LogError("Failed to locate player UI"); return; } } @@ -224,16 +220,20 @@ private void SetPlayerToFreecamMode(EFT.Player localPlayer) // This means our character will be fully visible, while letting the camera move freely 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").Value; - if (playerBody != null) + if (localPlayer.PlayerBody != null) { - playerBody.PointOfView.Value = EPointOfView.FreeCamera; + localPlayer.PlayerBody.PointOfView.Value = EPointOfView.FreeCamera; localPlayer.GetComponent().UpdatePointOfView(); } - _gamePlayerOwner.enabled = false; - _freeCamScript.IsActive = true; + if (_gamePlayerOwner != null) + { + _gamePlayerOwner.enabled = false; + } + if (_freeCamScript != null) + { + _freeCamScript.IsActive = true; + } } /// @@ -242,16 +242,16 @@ private void SetPlayerToFreecamMode(EFT.Player localPlayer) /// private void SetPlayerToFirstPersonMode(EFT.Player localPlayer) { - _freeCamScript.IsActive = false; - - //if (FreecamPlugin.CameraRememberLastPosition.Value) - //{ - // _lastPosition = _mainCamera.transform.position; - // _lastRotation = _mainCamera.transform.rotation; - //} + if (_freeCamScript != null) + { + _freeCamScript.IsActive = true; + } // re-enable _gamePlayerOwner - _gamePlayerOwner.enabled = true; + if (_gamePlayerOwner != null) + { + _gamePlayerOwner.enabled = false; + } localPlayer.PointOfView = EPointOfView.FirstPerson; CameraClass.Instance.SetOcclusionCullingEnabled(true); @@ -262,12 +262,14 @@ private void SetPlayerToFirstPersonMode(EFT.Player localPlayer) /// Gets the current instance if it's available /// /// Local instance; returns null if the game is not in raid - private EFT.Player GetLocalPlayerFromWorld() + private EFT.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.Instance; + GameWorld gameWorld = Singleton.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; @@ -284,7 +286,7 @@ public void DisableAndDestroyEffect(MonoBehaviour effect) public void OnDestroy() { - GameObject.Destroy(CameraParent); + Destroy(CameraParent); // Destroy FreeCamScript before FreeCamController if exists Destroy(_freeCamScript);