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