diff --git a/README.md b/README.md index d5bfa16..106272c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,33 @@ # Freecam -A freecam mode SPT-AKI \ No newline at end of file +An SPT-AKI modules mod that allows you to detach the camera and fly around freely in Escape From Tarkov. + +### Controls + +For now, the keybinds are non-configurable (unless you change it in code and build from source). +Keypad Plus - toggle free camera mode +Keypad Enter - teleport player to camera position +Keypad Multiply - toggle UI + +### How to install + +1. Download the latest release here: [link](https://dev.sp-tarkov.com/Terkoiz/Freecam/releases) -OR- build from source (instructions below) +2. Simply drop the folder `Terkoiz-Freecam` into your SPT-AKI `user/mods/` directory. + +### Known issues + +1. Your weapon doesn't turn invisible when you enter freecam mode +2. When teleporting to camera position, the camera rotation gets copied exactly, potentially causing the player model to tilt +3. Game version UI element is not hidden when toggling UI +4. None of the camera settings (speed, senstivity, etc.) are user-configurable +5. When flying to distant parts of the map in freecam mode, LODs are not triggered (these seem to follow the player) + +### How to build from source + +1. Download/clone this repository +2. Download/clone the `SPT-AKI/Modules` repository: [link](https://dev.sp-tarkov.com/SPT-AKI/Modules) +3. Move the contents of the `project` folder over to the SPT-AKI Modules `project` folder +4. Add the `Terkoiz.Freecam` project to the SPT-AKI Modules solution +5. Build modules - `Terkoiz.Freecam.dll` should appear under `Build/EscapeFromTarkov_Data/Managed` in the SPT-AKI Modules directory +6. Copy the .dll into the `mod/Terkoiz-Freecam` folder and rename to `modules.dll` +7. That's it, you have a working release of this mod! Follow the `How to install` instructions on how to get the mod into your game \ No newline at end of file diff --git a/mod/Terkoiz-Freecam/LICENSE b/mod/Terkoiz-Freecam/LICENSE new file mode 100644 index 0000000..32560ba --- /dev/null +++ b/mod/Terkoiz-Freecam/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2021 Martynas Gestautas. All rights reserved. + +Developed by: SPT-Aki + Martynas Gestautas + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal with the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + +* Neither the names of Martynas Gestautas, SPT-Aki nor the names of its + contributors may be used to endorse or promote products derived from + this Software without specific prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH +THE SOFTWARE. \ No newline at end of file diff --git a/mod/Terkoiz-Freecam/disclaimer.pdf b/mod/Terkoiz-Freecam/disclaimer.pdf new file mode 100644 index 0000000..4ad7dc5 Binary files /dev/null and b/mod/Terkoiz-Freecam/disclaimer.pdf differ diff --git a/mod/Terkoiz-Freecam/module.dll b/mod/Terkoiz-Freecam/module.dll new file mode 100644 index 0000000..af97e98 Binary files /dev/null and b/mod/Terkoiz-Freecam/module.dll differ diff --git a/mod/Terkoiz-Freecam/package.json b/mod/Terkoiz-Freecam/package.json new file mode 100644 index 0000000..822b1a1 --- /dev/null +++ b/mod/Terkoiz-Freecam/package.json @@ -0,0 +1,7 @@ +{ + "name": "Freecam", + "author": "Terkoiz", + "version": "1.0.0", + "license": "NCSA Open Source", + "dependencies": {} +} \ No newline at end of file diff --git a/project/Terkoiz.Freecam/Freecam.cs b/project/Terkoiz.Freecam/Freecam.cs new file mode 100644 index 0000000..70c438a --- /dev/null +++ b/project/Terkoiz.Freecam/Freecam.cs @@ -0,0 +1,103 @@ +using UnityEngine; + +namespace Terkoiz.Freecam +{ + /// + /// A simple free camera to be added to a Unity game object. + /// + /// Full credit to Ashley Davis on GitHub for the inital code: + /// https://gist.github.com/ashleydavis/f025c03a9221bc840a2b + /// + /// + public class Freecam : MonoBehaviour + { + /// + /// Normal speed of camera movement. + /// + public float MovementSpeed = 10f; + + /// + /// Speed of camera movement when shift is held down. + /// + public float FastMovementSpeed = 100f; + + /// + /// Sensitivity for free look. + /// + public float FreeLookSensitivity = 3f; + + /// + /// Amount to zoom the camera when using the mouse wheel. + /// + public float ZoomSensitivity = 10f; + + /// + /// Amount to zoom the camera when using the mouse wheel (fast mode). + /// + public float FastZoomSensitivity = 50f; + + public bool IsActive = false; + + void Update() + { + if (!IsActive) + { + return; + } + + var fastMode = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + var movementSpeed = fastMode ? FastMovementSpeed : MovementSpeed; + + if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) + { + transform.position += (-transform.right * movementSpeed * Time.deltaTime); + } + + if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) + { + transform.position += (transform.right * movementSpeed * Time.deltaTime); + } + + if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) + { + transform.position += (transform.forward * movementSpeed * Time.deltaTime); + } + + if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) + { + transform.position += (-transform.forward * movementSpeed * Time.deltaTime); + } + + if (Input.GetKey(KeyCode.Q)) + { + transform.position += (transform.up * movementSpeed * Time.deltaTime); + } + + 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); + } + + 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") * FreeLookSensitivity; + float newRotationY = transform.localEulerAngles.x - Input.GetAxis("Mouse Y") * FreeLookSensitivity; + transform.localEulerAngles = new Vector3(newRotationY, newRotationX, 0f); + + float axis = Input.GetAxis("Mouse ScrollWheel"); + if (axis != 0) + { + var zoomSensitivity = fastMode ? FastZoomSensitivity : ZoomSensitivity; + transform.position += transform.forward * axis * zoomSensitivity; + } + } + } +} \ No newline at end of file diff --git a/project/Terkoiz.Freecam/FreecamController.cs b/project/Terkoiz.Freecam/FreecamController.cs new file mode 100644 index 0000000..c0244c5 --- /dev/null +++ b/project/Terkoiz.Freecam/FreecamController.cs @@ -0,0 +1,163 @@ +using Comfort.Common; +using EFT; +using EFT.UI; +using UnityEngine; + +namespace Terkoiz.Freecam +{ + public class FreecamController : MonoBehaviour + { + private GameObject mainCamera; + private Freecam freeCamScript; + + private BattleUIScreen playerUi; + private bool uiHidden; + + // TODO: + // Menu for adjusting settings + // Config file for default settings + // Configurable keybinds + + // TODO MAYBE: + // Hide player weapon + // Hide version number UI element + // FreeCam controller support (camera could be smoother with an analog stick, apparently) + // Adjusting speed/sensitivity without a menu (Ctrl+ScrollWheel for example) + + public void Update() + { + if (Input.GetKeyDown(KeyCode.KeypadMultiply)) + { + ToggleUi(); + } + + if (Input.GetKeyDown(KeyCode.KeypadPlus)) + { + ToggleCamera(); + } + + if (Input.GetKeyDown(KeyCode.KeypadEnter)) + { + MovePlayerToCamera(); + } + } + + /// + /// Toggles the Freecam mode + /// + private void ToggleCamera() + { + // Get our own Player instance. Null means we're not in a raid + var localPlayer = GetLocalPlayerFromWorld(); + if (localPlayer == null) + { + return; + } + + // If we don't have the main camera object cached, go look for it in the scene + if (mainCamera == null) + { + // Finding a GameObject by name directly is apparantly 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) + { + Debug.LogError("Terkoiz.Freecam: ERROR: 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) + { + freeCamScript = mainCamera.AddComponent(); + } + + // 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) + { + localPlayer.gameObject.SetActive(false); + freeCamScript.IsActive = true; + } + else + { + freeCamScript.IsActive = false; + localPlayer.gameObject.SetActive(true); + } + } + + /// + /// When triggered during Freecam mode, teleports the player to where the camera was and exits Freecam mode + /// + private void MovePlayerToCamera() + { + var localPlayer = GetLocalPlayerFromWorld(); + if (localPlayer == null) + { + return; + } + + // 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) + { + return; + } + + // We basically do what ToggleCamera() does, but with extra code inbetween + 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); + } + } + + /// + /// Hides the main UI (health, stamina, stance, hotbar, etc.) + /// + 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(); + + if (playerUi == null) + { + PreloaderUI.Instance.Console.AddLog("ERROR: Failed to locate player UI", "FreeCam"); + return; + } + } + + playerUi.gameObject.SetActive(uiHidden); + uiHidden = !uiHidden; + } + + /// + /// Gets the current instance if it's available + /// + /// Local instance; returns null if the game is not in raid + private static 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; + if (gameWorld == null || gameWorld.RegisteredPlayers == null) + { + return null; + } + + // One of the RegisterdPlayers will have the IsYourPlayer flag set, which will be our own Player instance + return gameWorld.RegisteredPlayers.Find(p => p.IsYourPlayer); + } + } +} \ No newline at end of file diff --git a/project/Terkoiz.Freecam/Program.cs b/project/Terkoiz.Freecam/Program.cs new file mode 100644 index 0000000..21a6da5 --- /dev/null +++ b/project/Terkoiz.Freecam/Program.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +namespace Terkoiz.Freecam +{ + public class Program + { + private static GameObject HookObject; + + static void Main(string[] args) + { + Debug.LogError("Terkoiz.Freecam: Loading..."); + HookObject = new GameObject(); + HookObject.AddComponent(); + Object.DontDestroyOnLoad(HookObject); + } + } +} diff --git a/project/Terkoiz.Freecam/Properties/AssemblyInfo.cs b/project/Terkoiz.Freecam/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..92fb4d3 --- /dev/null +++ b/project/Terkoiz.Freecam/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Terkoiz.Freecam")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Terkoiz.Freecam")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("be2de623-48ff-4807-9696-167a17787718")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/project/Terkoiz.Freecam/Terkoiz.Freecam.csproj b/project/Terkoiz.Freecam/Terkoiz.Freecam.csproj new file mode 100644 index 0000000..56fc12e --- /dev/null +++ b/project/Terkoiz.Freecam/Terkoiz.Freecam.csproj @@ -0,0 +1,83 @@ + + + + + Debug + AnyCPU + {BE2DE623-48FF-4807-9696-167A17787718} + Library + Properties + Terkoiz.Freecam + Terkoiz.Freecam + v4.6.1 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\Shared\References\0Harmony.dll + False + + + ..\Shared\References\Assembly-CSharp.dll + False + + + ..\Shared\References\ItemComponent.Types.dll + False + + + ..\Shared\References\Comfort.dll + False + + + False + ..\Shared\References\Newtonsoft.Json.dll + False + + + + ..\Shared\References\UnityEngine.dll + False + + + ..\Shared\References\UnityEngine.CoreModule.dll + False + + + ..\Shared\References\UnityEngine.PhysicsModule.dll + False + + + + + + + + + + + {7584f43a-5937-417e-abf4-c5f680f300fb} + Aki.Common + False + + + + \ No newline at end of file