Initial mod file commit

This commit is contained in:
Martynas Gestautas 2021-06-02 23:05:01 +03:00
parent 03c3ae4eb2
commit 02f3312e83
10 changed files with 471 additions and 1 deletions

View File

@ -1,3 +1,33 @@
# Freecam # Freecam
A freecam mode SPT-AKI 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

View File

@ -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.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,7 @@
{
"name": "Freecam",
"author": "Terkoiz",
"version": "1.0.0",
"license": "NCSA Open Source",
"dependencies": {}
}

View File

@ -0,0 +1,103 @@
using UnityEngine;
namespace Terkoiz.Freecam
{
/// <summary>
/// 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
///
/// </summary>
public class Freecam : MonoBehaviour
{
/// <summary>
/// Normal speed of camera movement.
/// </summary>
public float MovementSpeed = 10f;
/// <summary>
/// Speed of camera movement when shift is held down.
/// </summary>
public float FastMovementSpeed = 100f;
/// <summary>
/// Sensitivity for free look.
/// </summary>
public float FreeLookSensitivity = 3f;
/// <summary>
/// Amount to zoom the camera when using the mouse wheel.
/// </summary>
public float ZoomSensitivity = 10f;
/// <summary>
/// Amount to zoom the camera when using the mouse wheel (fast mode).
/// </summary>
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;
}
}
}
}

View File

@ -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();
}
}
/// <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 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<Freecam>();
}
// 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);
}
}
/// <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;
}
// 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);
}
}
/// <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)
{
PreloaderUI.Instance.Console.AddLog("ERROR: Failed to locate player UI", "FreeCam");
return;
}
}
playerUi.gameObject.SetActive(uiHidden);
uiHidden = !uiHidden;
}
/// <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 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<GameWorld>.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);
}
}
}

View File

@ -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<FreecamController>();
Object.DontDestroyOnLoad(HookObject);
}
}
}

View File

@ -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")]

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{BE2DE623-48FF-4807-9696-167A17787718}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Terkoiz.Freecam</RootNamespace>
<AssemblyName>Terkoiz.Freecam</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\Shared\References\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\Shared\References\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ItemComponent.Types">
<HintPath>..\Shared\References\ItemComponent.Types.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Comfort">
<HintPath>..\Shared\References\Comfort.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Shared\References\Newtonsoft.Json.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="UnityEngine">
<HintPath>..\Shared\References\UnityEngine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\Shared\References\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>..\Shared\References\UnityEngine.PhysicsModule.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Freecam.cs" />
<Compile Include="FreecamController.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj">
<Project>{7584f43a-5937-417e-abf4-c5f680f300fb}</Project>
<Name>Aki.Common</Name>
<Private>False</Private>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>