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);