diff --git a/.gitignore b/.gitignore
index a26aa530..52352c9c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -236,6 +236,8 @@ Vectrosity.dll
/.vs/CameraTools/v15/sqlite3/storage.ide
/.vs/CameraTools
/.vs
-/CameraTools/bin/Release/KSPTrackIR.dll
-/CameraTools/bin/Release/KSPAssets.XmlSerializers.dll
-/CameraTools/bin/Release/Ionic.Zip.dll
+/.vscode
+/.envrc
+/CameraTools/Distribution/GameData/CameraTools/Plugins
+/CameraTools/bin/Debug
+/CameraTools/bin/Release
diff --git a/CameraTools/CCInputUtils.cs b/CameraTools/CCInputUtils.cs
deleted file mode 100644
index 13f06b51..00000000
--- a/CameraTools/CCInputUtils.cs
+++ /dev/null
@@ -1,167 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.18449
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-using System;
-using UnityEngine;
-
-namespace CameraTools
-{
- public class CCInputUtils
- {
- public static string GetInputString()
- {
- //keyCodes
- string[] names = System.Enum.GetNames(typeof(KeyCode));
- int numberOfKeycodes = names.Length;
-
- for(int i = 0; i < numberOfKeycodes; i++)
- {
- string output = names[i];
-
- if(output.Contains("Keypad"))
- {
- output = "["+output.Substring(6).ToLower()+"]";
- }
- else if(output.Contains("Alpha"))
- {
- output = output.Substring(5);
- }
- else //lower case key
- {
- output = output.ToLower();
- }
-
- //modifiers
- if(output.Contains("control"))
- {
- output = output.Split('c')[0] + " ctrl";
- }
- else if(output.Contains("alt"))
- {
- output = output.Split('a')[0] + " alt";
- }
- else if(output.Contains("shift"))
- {
- output = output.Split ('s')[0] + " shift";
- }
- else if(output.Contains("command"))
- {
- output = output.Split('c')[0]+" cmd";
- }
-
-
- //special keys
- else if(output == "backslash")
- {
- output = @"\";
- }
- else if(output == "backquote")
- {
- output = "`";
- }
- else if(output == "[period]")
- {
- output = "[.]";
- }
- else if(output == "[plus]")
- {
- output = "[+]";
- }
- else if(output == "[multiply]")
- {
- output = "[*]";
- }
- else if(output == "[divide]")
- {
- output = "[/]";
- }
- else if(output == "[minus]")
- {
- output = "[-]";
- }
- else if(output == "[enter]")
- {
- output = "enter";
- }
- else if(output.Contains("page"))
- {
- output = output.Insert(4, " ");
- }
- else if(output.Contains("arrow"))
- {
- output = output.Split('a')[0];
- }
- else if(output == "capslock")
- {
- output = "caps lock";
- }
- else if(output == "minus")
- {
- output = "-";
- }
-
- //test if input is valid
- try
- {
- if(Input.GetKey(output))
- {
- return output;
- }
- }
- catch(System.Exception)
- {
- }
-
- }
-
- //mouse
- for(int m = 0; m < 6; m++)
- {
- string inputString = "mouse "+m;
- try
- {
- if(Input.GetKey(inputString))
- {
- return inputString;
- }
- }
- catch(UnityException)
- {
- Debug.Log ("Invalid mouse: "+inputString);
- }
- }
-
- //joysticks
- for(int j = 1; j < 12; j++)
- {
- for(int b = 0; b<20; b++)
- {
- string inputString = "joystick "+j+" button "+b;
- try
- {
- if(Input.GetKey(inputString))
- {
- return inputString;
- }
- }
- catch(UnityException)
- {
- return string.Empty;
- }
-
- }
- }
-
- return string.Empty;
- }
-
-
- }
-}
-
diff --git a/CameraTools/CTAtmosphericAudioController.cs b/CameraTools/CTAtmosphericAudioController.cs
index aadeb0bf..835198aa 100644
--- a/CameraTools/CTAtmosphericAudioController.cs
+++ b/CameraTools/CTAtmosphericAudioController.cs
@@ -1,170 +1,222 @@
using UnityEngine;
+using System.Collections.Generic;
namespace CameraTools
{
public class CTAtmosphericAudioController : MonoBehaviour
{
+ static readonly Dictionary audioClips = new();
+
AudioSource windAudioSource;
AudioSource windHowlAudioSource;
AudioSource windTearAudioSource;
AudioSource sonicBoomSource;
+ AudioSource delayedSonicBoomSource;
Vessel vessel;
- bool playedBoom = false;
+ bool playedBoom = true;
+ bool sleep = false; // For when the SoundManager freaks out about running out of virtual channels.
+ float startedSleepAt = 0f;
+ float sleepDuration = 0f;
void Awake()
{
vessel = GetComponent();
- windAudioSource = gameObject.AddComponent();
+
+ windAudioSource = new GameObject("windAS").AddComponent();
windAudioSource.minDistance = 10;
windAudioSource.maxDistance = 10000;
windAudioSource.dopplerLevel = .35f;
windAudioSource.spatialBlend = 1;
- AudioClip windclip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/windloop");
- if(!windclip)
+ if (!audioClips.TryGetValue("CameraTools/Sounds/windloop", out AudioClip windclip))
+ {
+ windclip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/windloop");
+ audioClips["CameraTools/Sounds/windloop"] = windclip;
+ }
+ if (!windclip)
{
- Destroy (this);
+ Destroy(this);
return;
}
windAudioSource.clip = windclip;
+ windAudioSource.transform.parent = vessel.transform;
- windHowlAudioSource = gameObject.AddComponent();
+ windHowlAudioSource = new GameObject("windHowlAS").AddComponent();
windHowlAudioSource.minDistance = 10;
windHowlAudioSource.maxDistance = 7000;
windHowlAudioSource.dopplerLevel = .5f;
windHowlAudioSource.pitch = 0.25f;
- windHowlAudioSource.clip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/windhowl");
+ if (!audioClips.TryGetValue("CameraTools/Sounds/windhowl", out AudioClip windhowlclip))
+ {
+ windhowlclip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/windhowl");
+ audioClips["CameraTools/Sounds/windhowl"] = windhowlclip;
+ }
+ windHowlAudioSource.clip = windhowlclip;
windHowlAudioSource.spatialBlend = 1;
+ windHowlAudioSource.transform.parent = vessel.transform;
- windTearAudioSource = gameObject.AddComponent();
+ windTearAudioSource = new GameObject("windTearAS").AddComponent();
windTearAudioSource.minDistance = 10;
windTearAudioSource.maxDistance = 5000;
windTearAudioSource.dopplerLevel = 0.45f;
windTearAudioSource.pitch = 0.65f;
- windTearAudioSource.clip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/windtear");
+ if (!audioClips.TryGetValue("CameraTools/Sounds/windtear", out AudioClip windtearclip))
+ {
+ windtearclip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/windtear");
+ audioClips["CameraTools/Sounds/windtear"] = windtearclip;
+ }
+ windTearAudioSource.clip = windtearclip;
windTearAudioSource.spatialBlend = 1;
+ windTearAudioSource.transform.parent = vessel.transform;
- sonicBoomSource = new GameObject().AddComponent();
- sonicBoomSource.transform.parent = vessel.transform;
+ sonicBoomSource = new GameObject("sonicBoomAS").AddComponent();
sonicBoomSource.transform.localPosition = Vector3.zero;
sonicBoomSource.minDistance = 50;
sonicBoomSource.maxDistance = 20000;
sonicBoomSource.dopplerLevel = 0;
- sonicBoomSource.clip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/sonicBoom");
- sonicBoomSource.volume = Mathf.Clamp01(vessel.GetTotalMass()/4f);
- sonicBoomSource.Stop();
- sonicBoomSource.spatialBlend = 1;
-
- float angleToCam = Vector3.Angle(vessel.srf_velocity, FlightCamera.fetch.mainCamera.transform.position - vessel.transform.position);
- angleToCam = Mathf.Clamp(angleToCam, 1, 180);
- if(vessel.srfSpeed / (angleToCam) < 3.67f)
+ if (!audioClips.TryGetValue("CameraTools/Sounds/sonicBoom", out AudioClip sonicBoomclip))
{
- playedBoom = true;
+ sonicBoomclip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/sonicBoom");
+ audioClips["CameraTools/Sounds/sonicBoom"] = sonicBoomclip;
}
+ sonicBoomSource.clip = sonicBoomclip;
+ sonicBoomSource.volume = Mathf.Clamp01(vessel.GetTotalMass() / 4f);
+ sonicBoomSource.Stop();
+ sonicBoomSource.spatialBlend = 1;
+ sonicBoomSource.transform.parent = vessel.transform;
+ delayedSonicBoomSource = Instantiate(sonicBoomSource);
+ Reset();
CamTools.OnResetCTools += OnResetCTools;
}
+ ///
+ /// Reset some stuff in case we're not a new module.
+ /// Also helps when cleaning up to prevent extra booming.
+ ///
+ void Reset()
+ {
+ playedBoom = true; // Default to true so that it doesn't play accidentally.
+ if (windAudioSource && windAudioSource.isPlaying) windAudioSource.Stop();
+ if (windHowlAudioSource && windHowlAudioSource.isPlaying) windHowlAudioSource.Stop();
+ if (windTearAudioSource && windTearAudioSource.isPlaying) windTearAudioSource.Stop();
+ if (sonicBoomSource && sonicBoomSource.isPlaying) sonicBoomSource.Stop();
+ if (delayedSonicBoomSource && delayedSonicBoomSource.isPlaying) delayedSonicBoomSource.Stop();
+ }
void FixedUpdate()
{
- if(!vessel)
- {
- return;
- }
- if(Time.timeScale > 0 && vessel.dynamicPressurekPa > 0)
+ if (vessel == null || !vessel.loaded || !vessel.isActiveAndEnabled) return; // Vessel is dead or not ready.
+ if (CamTools.flightCamera == null) return; // Flight camera is broken.
+ if (FlightGlobals.currentMainBody != null && vessel.altitude > FlightGlobals.currentMainBody.atmosphereDepth) return; // Vessel is outside the atmosphere.
+ if (sleep && Time.time - startedSleepAt < sleepDuration) return;
+ sleep = false;
+ if (!PauseMenu.isOpen && Time.timeScale > 0 && vessel.dynamicPressurekPa > 0)
{
float srfSpeed = (float)vessel.srfSpeed;
srfSpeed = Mathf.Min(srfSpeed, 550f);
- float angleToCam = Vector3.Angle(vessel.srf_velocity, FlightCamera.fetch.mainCamera.transform.position - vessel.transform.position);
- angleToCam = Mathf.Clamp(angleToCam, 1, 180);
-
+ float angleToCam = Mathf.Clamp(Vector3.Angle(vessel.srf_velocity, CamTools.flightCamera.transform.position - vessel.transform.position), 1, 180);
- float lagAudioFactor = (75000 / (Vector3.Distance(vessel.transform.position, FlightCamera.fetch.mainCamera.transform.position) * srfSpeed * angleToCam / 90));
+ // Some comments on what the lagAudioFactor and waveFrontFactor are based on would have been nice...
+ float lagAudioFactor = 75000 / (Vector3.Distance(vessel.transform.position, CamTools.flightCamera.transform.position) * srfSpeed * angleToCam / 90);
lagAudioFactor = Mathf.Clamp(lagAudioFactor * lagAudioFactor * lagAudioFactor, 0, 4);
lagAudioFactor += srfSpeed / 230;
- float waveFrontFactor = ((3.67f * angleToCam)/srfSpeed);
+ float waveFrontFactor = 3.67f * angleToCam / srfSpeed;
waveFrontFactor = Mathf.Clamp(waveFrontFactor * waveFrontFactor * waveFrontFactor, 0, 2);
-
- if(vessel.srfSpeed > CamTools.speedOfSound)
+ if (vessel.srfSpeed > CamTools.speedOfSound)
{
- waveFrontFactor = (srfSpeed / (angleToCam) < 3.67f) ? waveFrontFactor + ((srfSpeed/(float)CamTools.speedOfSound)*waveFrontFactor) : 0;
- if(waveFrontFactor > 0)
+ waveFrontFactor = (srfSpeed / angleToCam < 3.67f) ? waveFrontFactor + srfSpeed / (float)CamTools.speedOfSound * waveFrontFactor : 0;
+
+ var cameraOffset = CamTools.flightCamera.transform.position - vessel.transform.position; // d
+ var dDotV = Vector3.Dot(vessel.srf_vel_direction, cameraOffset); // dot(d, v) = |d| * |v| * cos(θ) with normalised v
+ var dDotVsqr = dDotV * dDotV;
+ var sinAlpha = CamTools.speedOfSound / vessel.srfSpeed; // sin(α) = Vsnd / |v|
+ var threshold = cameraOffset.sqrMagnitude * (1f - sinAlpha * sinAlpha); // θ > π/2 && cos²(θ) > cos²(α)
+ if (dDotV < 0 && dDotVsqr > threshold) // Behind the wave front.
{
- if(!playedBoom)
+ if (!playedBoom)
{
- sonicBoomSource.transform.position = vessel.transform.position + (-vessel.srf_velocity);
- sonicBoomSource.PlayOneShot(sonicBoomSource.clip);
+ sonicBoomSource.transform.position = vessel.transform.position - vessel.srf_velocity * cameraOffset.magnitude / CamTools.speedOfSound;
+ delayedSonicBoomSource.transform.position = sonicBoomSource.transform.position;
+ sonicBoomSource.Play();
+ delayedSonicBoomSource.PlayDelayed(vessel.vesselSize.z / (float)vessel.srfSpeed); // Sonic booms are generally N-wave shaped, giving a double boom. (vesselSize.z is vessel length.)
+ if (CamTools.DEBUG) Debug.Log($"[CameraTools]: Behind the wavefront, playing N-wave sonic boom with interval {vessel.vesselSize.z / (float)vessel.srfSpeed:G3}s for {vessel.vesselName}.");
+ playedBoom = true;
}
- playedBoom = true;
}
- else
+ else if (dDotV > 0 || dDotVsqr < threshold * 0.9f) // In front of the wave front (with enough tolerance to not immediately trigger again).
{
-
+ if (CamTools.DEBUG && playedBoom) Debug.Log($"[CameraTools]: In front of the wavefront, resetting sonic boom trigger for {vessel.vesselName}.");
+ playedBoom = false;
}
}
- else if(CamTools.speedOfSound / (angleToCam) < 3.67f)
+ else if (vessel.srfSpeed < CamTools.speedOfSound * 0.95f) // Subsonic (with hysteresis).
{
+ if (CamTools.DEBUG && !playedBoom) Debug.Log($"[CameraTools]: Disabling sonic boom trigger for {vessel.vesselName} due to being subsonic.");
playedBoom = true;
}
lagAudioFactor *= waveFrontFactor;
float sqrAccel = (float)vessel.acceleration.sqrMagnitude;
+ float vesselMass = vessel.GetTotalMass();
+ float dynamicPressurekPa = (float)vessel.dynamicPressurekPa;
//windloop
- if(!windAudioSource.isPlaying)
+ if (!windAudioSource.isPlaying)
{
windAudioSource.Play();
- //Debug.Log("vessel dynamic pressure: " + vessel.dynamicPressurekPa);
+ // Debug.Log("[CameraTools]: vessel dynamic pressure: " + vessel.dynamicPressurekPa);
+ if (!windAudioSource.isPlaying) { SleepFor(1f); return; }
}
- float pressureFactor = Mathf.Clamp01((float)vessel.dynamicPressurekPa / 50f);
- float massFactor = Mathf.Clamp01(vessel.GetTotalMass() / 60f);
+ float pressureFactor = Mathf.Clamp01(dynamicPressurekPa / 50f);
+ float massFactor = Mathf.Clamp01(vesselMass / 60f);
float gFactor = Mathf.Clamp(sqrAccel / 225, 0, 1.5f);
windAudioSource.volume = massFactor * pressureFactor * gFactor * lagAudioFactor;
-
//windhowl
- if(!windHowlAudioSource.isPlaying)
+ if (!windHowlAudioSource.isPlaying)
{
windHowlAudioSource.Play();
+ if (!windHowlAudioSource.isPlaying) { SleepFor(1f); return; }
}
- float pressureFactor2 = Mathf.Clamp01((float)vessel.dynamicPressurekPa / 20f);
- float massFactor2 = Mathf.Clamp01(vessel.GetTotalMass() / 30f);
+ float pressureFactor2 = Mathf.Clamp01(dynamicPressurekPa / 20f);
+ float massFactor2 = Mathf.Clamp01(vesselMass / 30f);
windHowlAudioSource.volume = pressureFactor2 * massFactor2 * lagAudioFactor;
windHowlAudioSource.maxDistance = Mathf.Clamp(lagAudioFactor * 2500, windTearAudioSource.minDistance, 16000);
//windtear
- if(!windTearAudioSource.isPlaying)
+ if (!windTearAudioSource.isPlaying)
{
windTearAudioSource.Play();
+ if (!windTearAudioSource.isPlaying) { SleepFor(1f); return; }
}
- float pressureFactor3 = Mathf.Clamp01((float)vessel.dynamicPressurekPa / 40f);
- float massFactor3 = Mathf.Clamp01(vessel.GetTotalMass() / 10f);
+ float pressureFactor3 = Mathf.Clamp01(dynamicPressurekPa / 40f);
+ float massFactor3 = Mathf.Clamp01(vesselMass / 10f);
//float gFactor3 = Mathf.Clamp(sqrAccel / 325, 0.25f, 1f);
- windTearAudioSource.volume = pressureFactor3 * massFactor3;
+ windTearAudioSource.volume = pressureFactor3 * massFactor3 * Mathf.Clamp01(lagAudioFactor);
windTearAudioSource.minDistance = lagAudioFactor * 1;
windTearAudioSource.maxDistance = Mathf.Clamp(lagAudioFactor * 2500, windTearAudioSource.minDistance, 16000);
-
+
}
else
{
- if(windAudioSource.isPlaying)
+ if (windAudioSource.isPlaying)
{
windAudioSource.Stop();
}
- if(windHowlAudioSource.isPlaying)
+ if (windHowlAudioSource.isPlaying)
{
windHowlAudioSource.Stop();
}
- if(windTearAudioSource.isPlaying)
+ if (windTearAudioSource.isPlaying)
{
windTearAudioSource.Stop();
}
@@ -173,21 +225,37 @@ void FixedUpdate()
void OnDestroy()
{
- if(sonicBoomSource)
- {
- Destroy(sonicBoomSource.gameObject);
- }
+ if (sonicBoomSource) Destroy(sonicBoomSource.gameObject);
+ if (delayedSonicBoomSource) Destroy(delayedSonicBoomSource.gameObject);
+ if (windAudioSource) Destroy(windAudioSource.gameObject);
+ if (windHowlAudioSource) Destroy(windHowlAudioSource.gameObject);
+ if (windTearAudioSource) Destroy(windTearAudioSource.gameObject);
CamTools.OnResetCTools -= OnResetCTools;
}
void OnResetCTools()
{
- Destroy(windAudioSource);
- Destroy(windHowlAudioSource);
- Destroy(windTearAudioSource);
-
+ Reset(); // Prevent booming when switching vessels/restarting camera modes.
Destroy(this);
}
+
+ ///
+ /// Sleep for a bit to allow the SoundManager to recover from running out of channels.
+ ///
+ /// The duration to sleep for.
+ void SleepFor(float duration)
+ {
+ Debug.LogWarning($"[CameraTools]: Inhibiting wind audio for {duration}s due to technical difficulties.");
+ sleep = true;
+ startedSleepAt = Time.time;
+ sleepDuration = duration;
+ if (windAudioSource.isPlaying)
+ windAudioSource.Stop();
+ if (windHowlAudioSource.isPlaying)
+ windHowlAudioSource.Stop();
+ if (windTearAudioSource.isPlaying)
+ windTearAudioSource.Stop();
+ }
}
}
diff --git a/CameraTools/CTPartAudioController.cs b/CameraTools/CTPartAudioController.cs
index 17348766..2001316d 100644
--- a/CameraTools/CTPartAudioController.cs
+++ b/CameraTools/CTPartAudioController.cs
@@ -8,53 +8,41 @@ public class CTPartAudioController : MonoBehaviour
public AudioSource audioSource;
-
- float origMinDist = 1;
- float origMaxDist = 1;
-
- float modMinDist = 10;
- float modMaxDist = 10000;
-
- AudioRolloffMode origRolloffMode;
+ readonly float minDist = 10;
+ readonly float maxDist = 10000;
void Awake()
{
part = GetComponentInParent();
vessel = part.vessel;
-
- CamTools.OnResetCTools += OnResetCTools;
}
void Start()
{
- if(!audioSource)
+ if (!audioSource)
{
Destroy(this);
return;
}
- origMinDist = audioSource.minDistance;
- origMaxDist = audioSource.maxDistance;
- origRolloffMode = audioSource.rolloffMode;
- audioSource.rolloffMode = AudioRolloffMode.Logarithmic;
- audioSource.spatialBlend = 1;
-
+ CamTools.OnResetCTools += OnResetCTools;
}
void FixedUpdate()
{
- if(!audioSource)
+ if (!audioSource)
{
Destroy(this);
return;
}
- if(!part || !vessel)
+ if (!part || !vessel || !FlightCamera.fetch)
{
Destroy(this);
return;
}
+ if (!origSettingsStored) return; // Do nothing until the original settings get stored.
float angleToCam = Vector3.Angle(vessel.srf_velocity, FlightCamera.fetch.mainCamera.transform.position - vessel.transform.position);
angleToCam = Mathf.Clamp(angleToCam, 1, 180);
@@ -62,40 +50,91 @@ void FixedUpdate()
float srfSpeed = (float)vessel.srfSpeed;
srfSpeed = Mathf.Min(srfSpeed, 550f);
- float lagAudioFactor = (75000 / (Vector3.Distance(vessel.transform.position, FlightCamera.fetch.mainCamera.transform.position) * srfSpeed * angleToCam / 90));
+ float lagAudioFactor = 75000 / (Vector3.Distance(vessel.transform.position, FlightCamera.fetch.mainCamera.transform.position) * srfSpeed * angleToCam / 90);
lagAudioFactor = Mathf.Clamp(lagAudioFactor * lagAudioFactor * lagAudioFactor, 0, 4);
lagAudioFactor += srfSpeed / 230;
- float waveFrontFactor = ((3.67f * angleToCam)/srfSpeed);
+ float waveFrontFactor = 3.67f * angleToCam / srfSpeed;
waveFrontFactor = Mathf.Clamp(waveFrontFactor * waveFrontFactor * waveFrontFactor, 0, 2);
- if(vessel.srfSpeed > CamTools.speedOfSound)
+ if (vessel.srfSpeed > CamTools.speedOfSound)
{
- waveFrontFactor = (srfSpeed / (angleToCam) < 3.67f) ? waveFrontFactor + ((srfSpeed/(float)CamTools.speedOfSound)*waveFrontFactor): 0;
+ waveFrontFactor = (srfSpeed / angleToCam < 3.67f) ? waveFrontFactor + (srfSpeed / (float)CamTools.speedOfSound * waveFrontFactor) : 0;
}
lagAudioFactor *= waveFrontFactor;
-
- audioSource.minDistance = Mathf.Lerp(origMinDist, modMinDist * lagAudioFactor, Mathf.Clamp01((float)vessel.srfSpeed/30));
- audioSource.maxDistance = Mathf.Lerp(origMaxDist,Mathf.Clamp(modMaxDist * lagAudioFactor, audioSource.minDistance, 16000), Mathf.Clamp01((float)vessel.srfSpeed/30));
-
+
+ audioSource.minDistance = Mathf.Lerp(origMinDist, minDist * lagAudioFactor, Mathf.Clamp01((float)vessel.srfSpeed / 30));
+ audioSource.maxDistance = Mathf.Lerp(origMaxDist, Mathf.Clamp(maxDist * lagAudioFactor, audioSource.minDistance, 16000), Mathf.Clamp01((float)vessel.srfSpeed / 30));
}
void OnDestroy()
{
CamTools.OnResetCTools -= OnResetCTools;
+ }
+
+ #region Store/Restore Original settings.
+ bool origSettingsStored = false;
+ // Any settings that get adjusted in ApplyEffects should be added here.
+ float origMinDist;
+ float origMaxDist;
+ bool origBypassEffects;
+ bool origSpatialize;
+ float origSpatialBlend;
+ bool origSpatializePostEffects;
+ float origDopplerLevel;
+ AudioVelocityUpdateMode origVelocityUpdateMode;
+ AudioRolloffMode origRolloffMode;
-
+ public void StoreOriginalSettings()
+ {
+ if (audioSource == null) return;
+ origSettingsStored = true;
+ origMinDist = audioSource.minDistance;
+ origMaxDist = audioSource.maxDistance;
+ origBypassEffects = audioSource.bypassEffects;
+ origSpatialize = audioSource.spatialize;
+ origSpatialBlend = audioSource.spatialBlend;
+ origSpatializePostEffects = audioSource.spatializePostEffects;
+ origDopplerLevel = audioSource.dopplerLevel;
+ origVelocityUpdateMode = audioSource.velocityUpdateMode;
+ origRolloffMode = audioSource.rolloffMode;
}
- void OnResetCTools()
+ public void RestoreOriginalSettings()
{
+ if (!origSettingsStored || audioSource == null) return;
audioSource.minDistance = origMinDist;
audioSource.maxDistance = origMaxDist;
+ audioSource.bypassEffects = origBypassEffects;
+ audioSource.spatialize = origSpatialize;
+ audioSource.spatialBlend = origSpatialBlend;
+ audioSource.spatializePostEffects = origSpatializePostEffects;
+ audioSource.dopplerLevel = origDopplerLevel;
+ audioSource.velocityUpdateMode = origVelocityUpdateMode;
audioSource.rolloffMode = origRolloffMode;
- Destroy(this);
}
+ #endregion
+ public void ApplyEffects()
+ {
+ if (!origSettingsStored || audioSource == null) return;
+ audioSource.bypassEffects = false;
+ audioSource.spatialize = true;
+ audioSource.spatialBlend = 1;
+ audioSource.spatializePostEffects = true;
+ audioSource.dopplerLevel = 1;
+ audioSource.velocityUpdateMode = AudioVelocityUpdateMode.Fixed;
+ audioSource.rolloffMode = AudioRolloffMode.Logarithmic;
+ }
+ void OnResetCTools()
+ {
+ if (audioSource != null)
+ {
+ RestoreOriginalSettings();
+ }
+ Destroy(this);
+ }
}
}
diff --git a/CameraTools/CTPersistantFIeld.cs b/CameraTools/CTPersistantFIeld.cs
index 5f92cbc2..65c7e0e1 100644
--- a/CameraTools/CTPersistantFIeld.cs
+++ b/CameraTools/CTPersistantFIeld.cs
@@ -1,48 +1,70 @@
using System;
+using System.IO;
+using UnityEngine;
namespace CameraTools
{
[AttributeUsage(AttributeTargets.Field)]
public class CTPersistantField : Attribute
{
- public static string settingsURL = "GameData/CameraTools/settings.cfg";
+ static string oldSettingsURL = Path.GetFullPath(Path.Combine(KSPUtil.ApplicationRootPath, "GameData/CameraTools/settings.cfg")); // Migrate from the old settings file to the new one in PluginData so that we don't invalidate the ModuleManager cache.
+ public static string settingsURL = Path.GetFullPath(Path.Combine(KSPUtil.ApplicationRootPath, "GameData/CameraTools/PluginData/settings.cfg"));
- public CTPersistantField()
- {
-
- }
+ public CTPersistantField() { }
- public static void Save()
+ public static void Save(string section, Type type, object instance)
{
ConfigNode fileNode = ConfigNode.Load(settingsURL);
- ConfigNode settings = fileNode.GetNode("CToolsSettings");
+ if (fileNode == null)
+ fileNode = new ConfigNode();
+ if (!fileNode.HasNode(section))
+ fileNode.AddNode(section);
- foreach(var field in typeof(CamTools).GetFields())
+ ConfigNode settings = fileNode.GetNode(section);
+
+ foreach (var field in type.GetFields())
{
- if(!field.IsDefined(typeof(CTPersistantField), false)) continue;
+ if (field == null) continue;
+ if (!field.IsDefined(typeof(CTPersistantField), false)) continue;
- settings.SetValue(field.Name, field.GetValue(CamTools.fetch).ToString(), true);
+ settings.SetValue(field.Name, field.GetValue(instance).ToString(), true);
}
- fileNode.Save(settingsURL);
+ if (!Directory.GetParent(settingsURL).Exists)
+ { Directory.GetParent(settingsURL).Create(); }
+ var success = fileNode.Save(settingsURL);
+ if (success && File.Exists(oldSettingsURL)) // Remove the old settings if it exists and the new settings were saved.
+ { File.Delete(oldSettingsURL); }
}
- public static void Load()
+ public static void Load(string section, Type type, object instance)
{
ConfigNode fileNode = ConfigNode.Load(settingsURL);
- ConfigNode settings = fileNode.GetNode("CToolsSettings");
+ if (fileNode == null)
+ {
+ fileNode = ConfigNode.Load(oldSettingsURL); // Try the old location.
+ if (fileNode == null)
+ return; // No config file.
+ Debug.LogWarning("[CameraTools]: Loading settings from old config file. New config file is now in GameData/CameraTools/PluginData to improve compatibility with ModuleManager.");
+ }
- foreach(var field in typeof(CamTools).GetFields())
+ if (fileNode.HasNode(section))
{
- if(!field.IsDefined(typeof(CTPersistantField), false)) continue;
+ ConfigNode settings = fileNode.GetNode(section);
- if(settings.HasValue(field.Name))
+ foreach (var field in type.GetFields())
{
- object parsedValue = ParseValue(field.FieldType, settings.GetValue(field.Name));
- if(parsedValue != null)
+ if (field == null) continue;
+ if (!field.IsDefined(typeof(CTPersistantField), false)) continue;
+
+ if (settings.HasValue(field.Name))
{
- field.SetValue(CamTools.fetch, parsedValue);
+ object parsedValue = ParseValue(field.FieldType, settings.GetValue(field.Name));
+ if (parsedValue != null)
+ {
+ field.SetValue(instance, parsedValue);
+ }
}
}
}
@@ -50,30 +72,34 @@ public static void Load()
public static object ParseValue(Type type, string value)
{
- if(type == typeof(string))
+ if (type == typeof(string))
{
return value;
}
- if(type == typeof(bool))
+ if (type == typeof(bool))
{
return bool.Parse(value);
}
- else if(type.IsEnum)
+ else if (type.IsEnum)
{
return Enum.Parse(type, value);
}
- else if(type == typeof(float))
+ else if (type == typeof(int))
+ {
+ return int.Parse(value);
+ }
+ else if (type == typeof(float))
{
return float.Parse(value);
}
- else if(type == typeof(Single))
+ else if (type == typeof(Single))
{
return Single.Parse(value);
}
- UnityEngine.Debug.LogError("CameraTools failed to parse settings field of type "+type.ToString()+" and value "+value);
+ UnityEngine.Debug.LogError("[CameraTools]: Failed to parse settings field of type " + type.ToString() + " and value " + value);
return null;
}
diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs
index b0e95d42..1c59267a 100644
--- a/CameraTools/CamTools.cs
+++ b/CameraTools/CamTools.cs
@@ -1,197 +1,364 @@
-using System;
-using System.Collections;
+using KSP.UI.Screens;
using System.Collections.Generic;
+using System.Collections;
+using System.IO;
+using System.Linq;
+using System;
using UnityEngine;
-using System.Reflection;
-using KSP.UI.Screens;
+
+using CameraTools.ModIntegration;
+using CameraTools.Utils;
+
+using static CameraTools.Utils.StringUtils; // For direct access to Localize and LocalizeStr.
+using static CameraTools.Utils.CCInputUtils; // For direct access to GetKeyPress.
+
namespace CameraTools
{
[KSPAddon(KSPAddon.Startup.Flight, false)]
public class CamTools : MonoBehaviour
{
+ #region Fields
public static CamTools fetch;
+ public static FlightCamera flightCamera;
+ string Version = "unknown";
GameObject cameraParent;
- Vessel vessel;
+ public Vessel vessel;
+ List engines = new();
+ List cockpits = new();
+ public static HashSet ignoreVesselTypesForAudio = [VesselType.Debris, VesselType.SpaceObject, VesselType.Unknown, VesselType.Flag]; // Ignore some vessel types to avoid using up all the SoundManager's channels.
Vector3 origPosition;
Quaternion origRotation;
+ Vector3 origLocalPosition;
+ Quaternion origLocalRotation;
Transform origParent;
float origNearClip;
- FlightCamera flightCamera;
-
+ float origDistance;
+ FlightCamera.Modes origMode;
+ float origFov = 60;
Part camTarget = null;
-
- [CTPersistantField]
- public ReferenceModes referenceMode = ReferenceModes.Surface;
Vector3 cameraUp = Vector3.up;
-
- string fmUpKey = "[7]";
- string fmDownKey = "[1]";
- string fmForwardKey = "[8]";
- string fmBackKey = "[5]";
- string fmLeftKey = "[4]";
- string fmRightKey = "[6]";
- string fmZoomInKey = "[9]";
- string fmZoomOutKey = "[3]";
- //
-
-
- //current camera setting
- bool cameraToolActive = false;
-
-
- //GUI
+ public bool cameraToolActive = false;
+ bool cameraParentWasStolen = false;
+ bool autoEnableOverriden = false; // Override auto-enabling for various integrations, e.g., BDArmory.
+ bool revertWhenInFlightMode = false; // Revert the camera on returning to flight mode (if triggered in a different mode).
+ bool activateWhenInFlightMode = false; // Activate the camera on returning to flight mode (if triggered in a different mode).
+ System.Random rng;
+ Vessel.Situations lastVesselSituation = Vessel.Situations.FLYING;
+ [CTPersistantField] public static bool DEBUG = false;
+ [CTPersistantField] public static bool DEBUG2 = false;
+ [CTPersistantField] public static bool ShowTooltips = false;
+
+ string message;
+ bool vesselSwitched = false;
+ ToolModes switchToMode = ToolModes.DogfightCamera;
+ float vesselRadius = 0;
+ float PositionInterpolationTypeMax = Enum.GetNames(typeof(PositionInterpolationType)).Length - 1;
+ float RotationInterpolationTypeMax = Enum.GetNames(typeof(RotationInterpolationType)).Length - 1;
+
+ Vector3 upAxis;
+ Vector3 forwardAxis;
+ Vector3 rightAxis;
+
+ bool freeLook = false;
+ Vector2 freeLookStartUpDistance = Vector2.zero;
+ Vector3 freeLookOffset = Vector3.zero;
+ float freeLookDistance = 0;
+ [CTPersistantField] public float freeLookThresholdSqr = 0.1f; // Mouse movement threshold for starting free look (units unknown).
+
+ #region Input
+ [CTPersistantField] public string cameraKey = "home";
+ [CTPersistantField] public string revertKey = "end";
+ [CTPersistantField] public string toggleMenu = "[0]";
+ [CTPersistantField] public bool enableKeypad = false;
+ [CTPersistantField] public string fmUpKey = "[7]";
+ [CTPersistantField] public string fmDownKey = "[1]";
+ [CTPersistantField] public string fmForwardKey = "[8]";
+ [CTPersistantField] public string fmBackKey = "[5]";
+ [CTPersistantField] public string fmLeftKey = "[4]";
+ [CTPersistantField] public string fmRightKey = "[6]";
+ [CTPersistantField] public string fmZoomInKey = "[9]";
+ [CTPersistantField] public string fmZoomOutKey = "[3]";
+ [CTPersistantField] public string fmMovementModifier = "enter";
+ [CTPersistantField] public string fmModeToggleKey = "[2]";
+ [CTPersistantField] public string resetRollKey = "";
+ [CTPersistantField] public string fmPivotModeKey = "";
+ bool waitingForTarget = false;
+ bool waitingForPosition = false;
+ bool mouseUp = false;
+ bool editingKeybindings = false;
+ public enum FMModeTypes { Position, Speed };
+ [CTPersistantField] public FMModeTypes fmMode = FMModeTypes.Position;
+ readonly int FMModeTypesMax = Enum.GetValues(typeof(FMModeTypes)).Length - 1;
+ Vector4 fmSpeeds = Vector4.zero; // x,y,z,zoom.
+ public enum FMPivotModes { Camera, Target };
+ readonly int FMPivotModeMax = Enum.GetValues(typeof(FMPivotModes)).Length - 1;
+ [CTPersistantField] public FMPivotModes fmPivotMode = FMPivotModes.Camera;
+ #endregion
+
+ #region GUI
public static bool guiEnabled = false;
public static bool hasAddedButton = false;
+ [CTPersistantField] public static bool textInput = false;
bool updateFOV = false;
float windowWidth = 250;
float windowHeight = 400;
float draggableHeight = 40;
float leftIndent = 12;
float entryHeight = 20;
-
- [CTPersistantField]
- public ToolModes toolMode = ToolModes.StationaryCamera;
-
- Rect windowRect = new Rect(0,0,0,0);
+ float contentTop = 20;
+ float contentWidth;
+ float keyframeEditorWindowHeight = 160f;
+ [CTPersistantField] public ToolModes toolMode = ToolModes.StationaryCamera;
+ [CTPersistantField] public bool randomMode = false;
+ [CTPersistantField] public float randomModeDogfightChance = 70f;
+ [CTPersistantField] public float randomModeIVAChance = 10f;
+ [CTPersistantField] public float randomModeStationaryChance = 20f;
+ [CTPersistantField] public float randomModePathingChance = 0f;
+ Rect windowRect = new Rect(0, 0, 0, 0);
bool gameUIToggle = true;
float incrButtonWidth = 26;
-
- //stationary camera vars
- [CTPersistantField]
- public bool autoFlybyPosition = false;
- [CTPersistantField]
- public bool autoFOV = false;
- float manualFOV = 60;
- float currentFOV = 60;
- Vector3 manualPosition = Vector3.zero;
- [CTPersistantField]
- public float freeMoveSpeed = 10;
- string guiFreeMoveSpeed = "10";
- [CTPersistantField]
- public float keyZoomSpeed = 1;
- string guiKeyZoomSpeed = "1";
- float zoomFactor = 1;
- [CTPersistantField]
- public float zoomExp = 1;
- [CTPersistantField]
- public bool enableKeypad = false;
- [CTPersistantField]
- public float maxRelV = 2500;
-
+ [CTPersistantField] public bool manualOffset = false;
+ [CTPersistantField] public float manualOffsetForward = 500;
+ [CTPersistantField] public float manualOffsetRight = 50;
+ [CTPersistantField] public float manualOffsetUp = 5;
+ string guiOffsetForward = "500";
+ string guiOffsetRight = "50";
+ string guiOffsetUp = "5";
+ [CTPersistantField] public bool targetCoM = false;
+ static List> debugMessages = new();
+ public static void DebugLog(string m) => debugMessages.Add(new Tuple(Time.time, m));
+ Rect cShadowRect = new Rect(Screen.width * 3 / 5, 100, Screen.width / 3 - 50, 100);
+ Rect cDebugRect = new Rect(Screen.width * 3 / 5 + 2, 100 + 2, Screen.width / 3 - 50, 100);
+ GUIStyle cStyle;
+ GUIStyle cShadowStyle;
+ GUIStyle centerLabel;
+ GUIStyle leftLabel;
+ GUIStyle rightLabel;
+ GUIStyle leftLabelBold;
+ GUIStyle titleStyle;
+ GUIStyle inputFieldStyle;
+ GUIStyle watermarkStyle;
+ Dictionary inputFields;
+ readonly List> debug2Messages = new();
+ void Debug2Log(string m) => debug2Messages.Add(new Tuple(Time.time, m));
+ float lastSavedTime = 0;
+ [CTPersistantField] public float UIScale = 1;
+ [CTPersistantField] public bool UIScaleFollowsStock = true;
+ float _UIScale => UIScaleFollowsStock ? GameSettings.UI_SCALE : UIScale;
+ float previousUIScale = 1;
+ bool scalingUI = false;
+
+ #endregion
+
+ #region Revert/Reset
bool setPresetOffset = false;
Vector3 presetOffset = Vector3.zero;
+ [CTPersistantField] bool saveRotation = false;
bool hasSavedRotation = false;
Quaternion savedRotation;
- [CTPersistantField]
- public bool manualOffset = false;
- [CTPersistantField]
- public float manualOffsetForward = 500;
- [CTPersistantField]
- public float manualOffsetRight = 50;
- [CTPersistantField]
- public float manualOffsetUp = 5;
- string guiOffsetForward = "500";
- string guiOffsetRight = "50";
- string guiOffsetUp = "5";
-
- Vector3 lastVesselPosition = Vector3.zero;
+ bool wasActiveBeforeModeChange = false;
Vector3 lastTargetPosition = Vector3.zero;
bool hasTarget = false;
-
- [CTPersistantField]
- public bool useOrbital = false;
-
- [CTPersistantField]
- public bool targetCoM = false;
-
bool hasDied = false;
- float diedTime = 0;
- //vessel reference mode
- Vector3 initialVelocity = Vector3.zero;
- Vector3 initialPosition = Vector3.zero;
- Orbit initialOrbit = null;
- double initialUT;
-
//retaining position and rotation after vessel destruction
- Vector3 lastPosition;
- Quaternion lastRotation;
-
-
- //click waiting stuff
- bool waitingForTarget = false;
- bool waitingForPosition = false;
-
- bool mouseUp = false;
-
- //Keys
- [CTPersistantField]
- public string cameraKey = "home";
- [CTPersistantField]
- public string revertKey = "end";
+ GameObject deathCam;
+ Vector3 deathCamPosition; // Local copies to avoid interacting with the transform all the time.
+ Quaternion deathCamRotation;
+ Vector3 deathCamVelocity;
+ Vector3 deathCamTargetVelocity;
+ float deathCamDecayFactor = 0.8f;
+ Vessel deathCamTarget = null;
+ Vector3d floatingKrakenAdjustment = Vector3d.zero; // Position adjustment for Floating origin and Krakensbane velocity changes.
+ public delegate void ResetCTools();
+ public static event ResetCTools OnResetCTools;
+ #endregion
+ #region Recording
//recording input for key binding
bool isRecordingInput = false;
- bool isRecordingActivate = false;
- bool isRecordingRevert = false;
-
- Vector3 resetPositionFix;//fixes position movement after setting and resetting camera
-
- //floating origin shift handler
- Vector3d lastOffset = FloatingOrigin.fetch.offset;
+ bool boundThisFrame = false;
+ string currentlyBinding = "";
+ #endregion
+ #region Audio Fields
AudioSource[] audioSources;
- float[] originalAudioSourceDoppler;
+ List<(int index, float dopplerLevel, AudioVelocityUpdateMode velocityUpdateMode, bool bypassEffects, bool spatialize, float spatialBlend)> originalAudioSourceSettings = new();
+ HashSet excludeAudioSources = new() { "MusicLogic", "windAS", "windHowlAS", "windTearAS", "sonicBoomAS" }; // Don't adjust music or atmospheric audio.
bool hasSetDoppler = false;
+ bool hasSpatializerPlugin = false;
+ [CTPersistantField] public static bool disregardSpatializerCheck = false;
+ [CTPersistantField] public bool useAudioEffects = true;
+ [CTPersistantField] public bool enableVFX = true;
+ public static double speedOfSound = 340;
+ #endregion
+
+ #region Zoom
+ [CTPersistantField] public bool autoZoomDogfight = false;
+ [CTPersistantField] public bool autoZoomStationary = true;
+ public bool autoFOV
+ {
+ get
+ {
+ return toolMode switch
+ {
+ ToolModes.DogfightCamera => autoZoomDogfight,
+ ToolModes.StationaryCamera => autoZoomStationary,
+ _ => false
+ };
+ }
+ set
+ {
+ switch (toolMode)
+ {
+ case ToolModes.DogfightCamera:
+ autoZoomDogfight = value;
+ break;
+ case ToolModes.StationaryCamera:
+ autoZoomStationary = value;
+ break;
+ }
+ }
+ }
+ float manualFOV = 60;
+ float currentFOV = 60;
+ [CTPersistantField] public float autoZoomMarginDogfight = 50;
+ [CTPersistantField] public float autoZoomMarginStationary = 30;
+ [CTPersistantField] public float autoZoomMarginMax = 50f;
+ public float autoZoomMargin
+ {
+ get
+ {
+ return toolMode switch
+ {
+ ToolModes.DogfightCamera => autoZoomMarginDogfight,
+ ToolModes.StationaryCamera => autoZoomMarginStationary,
+ _ => 20f,
+ };
+ }
+ set
+ {
+ switch (toolMode)
+ {
+ case ToolModes.DogfightCamera:
+ autoZoomMarginDogfight = value;
+ break;
+ case ToolModes.StationaryCamera:
+ autoZoomMarginStationary = value;
+ break;
+ }
+ }
+ }
+ #endregion
- [CTPersistantField]
- public bool useAudioEffects = true;
-
- //camera shake
+ #region Camera Shake
Vector3 shakeOffset = Vector3.zero;
float shakeMagnitude = 0;
- [CTPersistantField]
- public float shakeMultiplier = 1;
-
- public delegate void ResetCTools();
- public static event ResetCTools OnResetCTools;
- public static double speedOfSound = 330;
-
- //dogfight cam
- Vessel dogfightPrevTarget;
- Vessel dogfightTarget;
- [CTPersistantField]
- float dogfightDistance = 30;
- [CTPersistantField]
- float dogfightOffsetX = 10;
- [CTPersistantField]
- float dogfightOffsetY = 4;
- float dogfightMaxOffset = 50;
- float dogfightLerp = 20;
- [CTPersistantField]
- float autoZoomMargin = 20;
+ [CTPersistantField] public float shakeMultiplier = 0;
+ #endregion
+
+ #region Dogfight Camera Fields
+ public enum DogfightOffsetMode { World, Camera, Vessel }
+ readonly int DogfightOffsetModeMax = Enum.GetValues(typeof(DogfightOffsetMode)).Length - 1;
+ public Vessel dogfightTarget;
+ [CTPersistantField] public float dogfightDistance = 50f;
+ [CTPersistantField] public float dogfightMaxDistance = 100;
+ [CTPersistantField] public float dogfightOffsetX = 0f;
+ [CTPersistantField] public float dogfightOffsetY = 5f;
+ [CTPersistantField] public float dogfightMaxOffset = 50;
+ [CTPersistantField] public bool dogfightInertialChaseMode = true;
+ [CTPersistantField] public DogfightOffsetMode dogfightOffsetMode = DogfightOffsetMode.Camera;
+ [CTPersistantField] public float dogfightLerp = 0.15f;
+ [CTPersistantField] public float dogfightRoll = 0.2f;
+ [CTPersistantField] public float dogfightInertialFactor = 0.5f;
+ [CTPersistantField] public bool dogfightChasePlaneMode = false;
+ bool chasePlaneTargetIsEVA = false;
+ Vector3 dogfightLerpDelta = default;
+ Vector3 dogfightLerpMomentum = default;
+ Vector3 dogfightRotationTarget = default;
+ Quaternion dogfightCameraRoll = Quaternion.identity;
+ Vector3 dogfightCameraRollUp = Vector3.up;
List loadedVessels;
bool showingVesselList = false;
bool dogfightLastTarget = false;
Vector3 dogfightLastTargetPosition;
Vector3 dogfightLastTargetVelocity;
bool dogfightVelocityChase = false;
- //bdarmory
- bool hasBDAI = false;
- [CTPersistantField]
- public bool useBDAutoTarget = false;
- object aiComponent = null;
- FieldInfo bdAiTargetField;
-
-
- //pathing
- int selectedPathIndex = -1;
+ bool cockpitView = false;
+ Vector3 mouseAimFlightTarget = default;
+ Vector3 mouseAimFlightTargetLocal = default;
+ #endregion
+
+ #region Stationary Camera Fields
+ [CTPersistantField] public bool autoLandingPosition = false;
+ bool autoLandingCamEnabled = false;
+ [CTPersistantField] public bool autoFlybyPosition = false;
+ Vector3 manualPosition = Vector3.zero;
+ Vector3 lastVesselCoM = Vector3.zero;
+ [CTPersistantField] public float freeMoveSpeed = 10;
+ string guiFreeMoveSpeed = "10";
+ float freeMoveSpeedRaw;
+ float freeMoveSpeedMinRaw;
+ float freeMoveSpeedMaxRaw;
+ [CTPersistantField] public float freeMoveSpeedMin = 0.1f;
+ [CTPersistantField] public float freeMoveSpeedMax = 100f;
+ [CTPersistantField] public float keyZoomSpeed = 1;
+ string guiKeyZoomSpeed = "1";
+ float zoomSpeedRaw;
+ float zoomSpeedMinRaw;
+ float zoomSpeedMaxRaw;
+ public float zoomFactor = 1;
+ [CTPersistantField] public float keyZoomSpeedMin = 0.01f;
+ [CTPersistantField] public float keyZoomSpeedMax = 10f;
+ [CTPersistantField] public float zoomExpDogfight = 1f;
+ [CTPersistantField] public float zoomExpStationary = 1f;
+ [CTPersistantField] public float zoomMax = 1000f;
+ float zoomMaxExp = 8f;
+ public float zoomExp
+ {
+ get
+ {
+ switch (toolMode)
+ {
+ case ToolModes.DogfightCamera: return zoomExpDogfight;
+ case ToolModes.StationaryCamera: return zoomExpStationary;
+ case ToolModes.Pathing: return zoomExpPathing;
+ default: return 1f;
+ }
+ }
+ set
+ {
+ switch (toolMode)
+ {
+ case ToolModes.DogfightCamera:
+ zoomExpDogfight = value;
+ break;
+ case ToolModes.StationaryCamera:
+ zoomExpStationary = value;
+ break;
+ case ToolModes.Pathing:
+ zoomExpPathing = value;
+ break;
+ }
+ }
+ }
+ [CTPersistantField] public float zoomExpPathing = 1f;
+ [CTPersistantField] public float maxRelV = 2500;
+ [CTPersistantField] public bool maintainInitialVelocity = false;
+ Vector3d initialVelocity = Vector3d.zero;
+ Orbit initialOrbit;
+ [CTPersistantField] public bool useOrbital = false;
+ float signedMaxRelVSqr;
+ #endregion
+
+ #region Pathing Camera Fields
+ [CTPersistantField] public int selectedPathIndex = -1;
List availablePaths;
CameraPath currentPath
{
get
{
- if(selectedPathIndex >= 0 && selectedPathIndex < availablePaths.Count)
+ if (selectedPathIndex >= 0 && selectedPathIndex < availablePaths.Count)
{
return availablePaths[selectedPathIndex];
}
@@ -203,608 +370,1017 @@ CameraPath currentPath
}
int currentKeyframeIndex = -1;
float currentKeyframeTime;
+ PositionInterpolationType currentKeyframePositionInterpolationType = PositionInterpolationType.CubicSpline; // Default to CubicSpline
+ RotationInterpolationType currentKeyframeRotationInterpolationType = RotationInterpolationType.CubicSpline; // Default to CubicSpline
string currKeyTimeString;
bool showKeyframeEditor = false;
float pathStartTime;
+ public float pathingSecondarySmoothing = 0f;
+ public float pathingLerpRate = 1; // Lerp rate corresponding to the secondary smoothing factor.
+ public float pathingTimeScale = 1f;
bool isPlayingPath = false;
float pathTime
{
get
{
- return Time.time - pathStartTime;
+ return GetTime() - pathStartTime;
}
}
Vector2 keysScrollPos;
+ public bool interpolationType = false;
+ [CTPersistantField] public bool useRealTime = true;
+ #endregion
+
+ #region Mod Integration
+ BDArmory bdArmory;
+ BetterTimeWarp betterTimeWarp;
+ TimeControl timeControl;
+ #endregion
+ #endregion
void Awake()
{
- if(fetch)
+ if (fetch)
{
Destroy(fetch);
}
fetch = this;
+ GetVersion();
Load();
- guiOffsetForward = manualOffsetForward.ToString();
- guiOffsetRight = manualOffsetRight.ToString();
- guiOffsetUp = manualOffsetUp.ToString();
- guiKeyZoomSpeed = keyZoomSpeed.ToString();
- guiFreeMoveSpeed = freeMoveSpeed.ToString();
+ rng = new System.Random();
}
void Start()
{
- windowRect = new Rect(Screen.width-windowWidth-40, 0, windowWidth, windowHeight);
+ windowRect = new Rect(Screen.width - _UIScale * windowWidth - Mathf.CeilToInt(GameSettings.UI_SCALE * 42), 0, windowWidth, windowHeight);
flightCamera = FlightCamera.fetch;
+ if (flightCamera == null)
+ {
+ Debug.LogError("[CameraTools.CamTools]: Flight Camera is null! Unable to start CameraTools!");
+ Destroy(this);
+ return;
+ }
cameraToolActive = false;
SaveOriginalCamera();
-
+
AddToolbarButton();
-
+
GameEvents.onHideUI.Add(GameUIDisable);
GameEvents.onShowUI.Add(GameUIEnable);
- //GameEvents.onGamePause.Add (PostDeathRevert);
- GameEvents.OnVesselRecoveryRequested.Add(PostDeathRevert);
- GameEvents.onFloatingOriginShift.Add(OnFloatingOriginShift);
GameEvents.onGameSceneLoadRequested.Add(PostDeathRevert);
-
- cameraParent = new GameObject("StationaryCameraParent");
- //cameraParent.SetActive(true);
- //cameraParent = (GameObject) Instantiate(cameraParent, Vector3.zero, Quaternion.identity);
-
- if(FlightGlobals.ActiveVessel != null)
- {
- cameraParent.transform.position = FlightGlobals.ActiveVessel.transform.position;
- vessel = FlightGlobals.ActiveVessel;
- CheckForBDAI(FlightGlobals.ActiveVessel);
+ cameraParent = new GameObject("CameraTools.CameraParent");
+ deathCam = new GameObject("CameraTools.DeathCam");
+
+ bdArmory = BDArmory.instance;
+ betterTimeWarp = BetterTimeWarp.instance;
+ timeControl = TimeControl.instance;
+ hasSpatializerPlugin = disregardSpatializerCheck || !string.IsNullOrEmpty(AudioSettings.GetSpatializerPluginName()); // Check for a spatializer plugin, otherwise doppler effects won't work.
+ if (DEBUG) { Debug.Log($"[CameraTools]: Spatializer plugin {(hasSpatializerPlugin ? $"found: {AudioSettings.GetSpatializerPluginName()}" : "not found, doppler effects disabled.")}"); }
+
+ if (FlightGlobals.ActiveVessel != null)
+ {
+ vessel = FlightGlobals.ActiveVessel;
+ cameraParent.transform.position = vessel.CoM;
+ deathCamPosition = vessel.CoM;
+ deathCamRotation = vessel.transform.rotation;
}
- bdAiTargetField = GetAITargetField();
GameEvents.onVesselChange.Add(SwitchToVessel);
+ GameEvents.onVesselWillDestroy.Add(CurrentVesselWillDestroy);
+ GameEvents.OnCameraChange.Add(CameraModeChange);
+ TimingManager.FixedUpdateAdd(TimingManager.TimingStage.BetterLateThanNever, KrakensbaneWarpCorrection); // Perform our Krakensbane corrections after KSP's floating origin/Krakensbane corrections have run.
+
+ // Styles and rects.
+ cStyle = new GUIStyle(HighLogic.Skin.label) { fontStyle = FontStyle.Bold, fontSize = 18, alignment = TextAnchor.UpperLeft };
+ cShadowStyle = new GUIStyle(cStyle);
+ cShadowRect = new Rect(cDebugRect);
+ cShadowRect.x += 2;
+ cShadowRect.y += 2;
+ cShadowStyle.normal.textColor = new Color(0, 0, 0, 0.75f);
+ centerLabel = new GUIStyle { alignment = TextAnchor.UpperCenter };
+ centerLabel.normal.textColor = Color.white;
+ leftLabel = new GUIStyle { alignment = TextAnchor.UpperLeft };
+ leftLabel.normal.textColor = Color.white;
+ rightLabel = new GUIStyle(leftLabel) { alignment = TextAnchor.UpperRight };
+ leftLabelBold = new GUIStyle(leftLabel) { fontStyle = FontStyle.Bold };
+ titleStyle = new GUIStyle(centerLabel) { fontSize = 24, alignment = TextAnchor.MiddleCenter };
+ watermarkStyle = new GUIStyle(leftLabel);
+ watermarkStyle.normal.textColor = XKCDColors.LightBlueGrey;
+ watermarkStyle.fontSize = 12;
+ contentWidth = windowWidth - 2 * leftIndent;
+
+ inputFields = new Dictionary {
+ {"autoZoomMargin", gameObject.AddComponent().Initialise(0, autoZoomMargin, 0f, autoZoomMarginMax, 4)},
+ {"zoomFactor", gameObject.AddComponent().Initialise(0, zoomFactor, 1f, zoomMax, 4)},
+ {"shakeMultiplier", gameObject.AddComponent().Initialise(0, shakeMultiplier, 0f, 10f, 1)},
+ {"dogfightDistance", gameObject.AddComponent().Initialise(0, dogfightDistance, 1f, dogfightMaxDistance, 3)},
+ {"dogfightOffsetX", gameObject.AddComponent().Initialise(0, dogfightOffsetX, -dogfightMaxOffset, dogfightMaxOffset, 3)},
+ {"dogfightOffsetY", gameObject.AddComponent().Initialise(0, dogfightOffsetY, -dogfightMaxOffset, dogfightMaxOffset, 3)},
+ {"dogfightLerp", gameObject.AddComponent().Initialise(0, dogfightLerp, 0.01f, 0.5f, 3)},
+ {"dogfightRoll", gameObject.AddComponent().Initialise(0, dogfightRoll, 0f, 1f, 3)},
+ {"dogfightInertialFactor", gameObject.AddComponent().Initialise(0, dogfightInertialFactor, 0f, 1f, 2)},
+ {"pathingSecondarySmoothing", gameObject.AddComponent().Initialise(0, pathingSecondarySmoothing, 0f, 1f, 4)},
+ {"pathingTimeScale", gameObject.AddComponent().Initialise(0, pathingTimeScale, 0.05f, 4f, 4)},
+ {"randomModeDogfightChance", gameObject.AddComponent().Initialise(0, randomModeDogfightChance, 0f, 100f, 3)},
+ {"randomModeIVAChance", gameObject.AddComponent().Initialise(0, randomModeIVAChance, 0f, 100f, 3)},
+ {"randomModeStationaryChance", gameObject.AddComponent().Initialise(0, randomModeStationaryChance, 0f, 100f, 3)},
+ {"randomModePathingChance", gameObject.AddComponent().Initialise(0, randomModePathingChance, 0f, 100f, 3)},
+ {"freeMoveSpeed", gameObject.AddComponent().Initialise(0, freeMoveSpeed, freeMoveSpeedMin, freeMoveSpeedMax, 4)},
+ {"keyZoomSpeed", gameObject.AddComponent().Initialise(0, keyZoomSpeed, keyZoomSpeedMin, keyZoomSpeedMax, 4)},
+ {"maxRelV", gameObject.AddComponent().Initialise(0, maxRelV, float.MinValue, float.MaxValue, 6)},
+ {"freeLookThresholdSqr", gameObject.AddComponent().Initialise(0, freeLookThresholdSqr, 0, 1, 4)},
+ {"UIScale", gameObject.AddComponent().Initialise(0, UIScale, 0.5f, 2f, 4)},
+ };
}
void OnDestroy()
{
+ GameEvents.onHideUI.Remove(GameUIDisable);
+ GameEvents.onShowUI.Remove(GameUIEnable);
+ GameEvents.onGameSceneLoadRequested.Remove(PostDeathRevert);
GameEvents.onVesselChange.Remove(SwitchToVessel);
+ GameEvents.onVesselWillDestroy.Remove(CurrentVesselWillDestroy);
+ GameEvents.OnCameraChange.Remove(CameraModeChange);
+ TimingManager.FixedUpdateRemove(TimingManager.TimingStage.BetterLateThanNever, KrakensbaneWarpCorrection);
+ Save();
}
-
- void Update()
+
+ void CameraModeChange(CameraManager.CameraMode mode)
{
- if(!isRecordingInput)
+ if (mode != CameraManager.CameraMode.Flight && CameraManager.Instance.previousCameraMode == CameraManager.CameraMode.Flight)
+ {
+ wasActiveBeforeModeChange = cameraToolActive;
+ cameraToolActive = false;
+ if (DEBUG && wasActiveBeforeModeChange) Debug.Log($"[CameraTools]: Deactivating due to switching to {mode} camera mode.");
+ }
+ else if (mode == CameraManager.CameraMode.Flight && CameraManager.Instance.previousCameraMode != CameraManager.CameraMode.Flight)
{
- if(Input.GetKeyDown(KeyCode.KeypadDivide))
+ if ((wasActiveBeforeModeChange || activateWhenInFlightMode) && !autoEnableOverriden && !bdArmory.autoEnableOverride)
{
- guiEnabled = !guiEnabled;
+ if (DEBUG) Debug.Log($"[CameraTools]: Camera mode changed to {mode} from {CameraManager.Instance.previousCameraMode}, reactivating {toolMode}.");
+ cockpitView = false; // Don't go back into cockpit view in case it was triggered by the user.
+ cameraToolActive = true;
+ RevertCamera();
+ flightCamera.transform.position = deathCamPosition;
+ flightCamera.transform.rotation = deathCamRotation;
+ if (!revertWhenInFlightMode)
+ {
+ if (CameraManager.Instance.previousCameraMode == CameraManager.CameraMode.Map) StartCoroutine(DelayActivation(1, false)); // Something messes with the camera position on the first frame after switching.
+ else CameraActivate();
+ }
}
-
- if(Input.GetKeyDown(revertKey))
+ else if (revertWhenInFlightMode)
{
- RevertCamera();
+ if (DEBUG) Debug.Log($"[CameraTools]: Camera mode changed to {mode} from {CameraManager.Instance.previousCameraMode}, applying delayed revert.");
+ cockpitView = false; // Don't go back into cockpit view in case it was triggered by the user.
+ cameraToolActive = true;
+ RevertCamera();
}
- else if(Input.GetKeyDown(cameraKey))
+ }
+ }
+
+ IEnumerator DelayActivation(int frames, bool fixedUpdate = false)
+ {
+ if (fixedUpdate)
+ {
+ var wait = new WaitForFixedUpdate();
+ for (int i = 0; i < frames; ++i) yield return wait;
+ }
+ else
+ {
+ var wait = new WaitForEndOfFrame();
+ for (int i = 0; i < frames; ++i) yield return wait;
+ }
+ CameraActivate();
+ }
+
+ bool wasUsingObtVel = false;
+ bool wasInHighWarp = false;
+ bool wasAbove1e5 = false;
+ float previousWarpFactor = 1;
+ // float δt = 0f;
+ void KrakensbaneWarpCorrection()
+ {
+ // Compensate for floating origin and Krakensbane velocity shifts under warp.
+ // Notes:
+ // This runs in the BetterLateThanNever timing phase after the flight integrator and floating origin/Krakensbane corrections have been applied.
+ // There is a small direction change in dogfight mode when leaving atmospheres due to switching between surface velocity and orbital velocity.
+ // At an altitude of 100km above a body (except on Jool, where it's in atmosphere), the Krakensbane velocity changes from the active vessel's surface velocity to its orbital velocity. I suspect this corresponds to KrakensbaneInstance.extraAltOffsetForVel, but Krakensbane doesn't provide an instance property.
+ // The stationary camera has a visible jitter when the target is a large distance away. I believe this is due to half-precision rounding of the graphics and there is a slow drift, which I believe is due to single-precision rounding of the camera position.
+ // Dogfight camera is now working perfectly.
+ // FIXME Stationary camera - maintain velocity
+ // - When changing low warp at >100km there is a slow drift, like the orbit calculation position is slightly wrong. I.e., starting at a given low warp and staying there is fine, but once changed the drift begins.
+ // - Below 100km, there is a small unsteady drift when not in high warp (exagerated by low warp) and once present continues after entering high warp.
+ // - Switching in and out of map mode isn't showing the vessel on returning.
+ if (GameIsPaused) return;
+ if (vessel == null || !vessel.gameObject.activeInHierarchy) return;
+ if (cameraToolActive)
+ {
+ var inHighWarp = (TimeWarp.WarpMode == TimeWarp.Modes.HIGH && TimeWarp.CurrentRate > 1);
+ var inLowWarp = !inHighWarp && TimeWarp.CurrentRate != 1;
+ var inOrbit = vessel.InOrbit();
+ var useObtVel = inHighWarp || (inOrbit && vessel.altitude > 1e5); // Unknown if this should be >= or not. Unlikely to be an issue though.
+ switch (toolMode)
{
- if(toolMode == ToolModes.StationaryCamera)
- {
- if(!cameraToolActive)
+ case ToolModes.DogfightCamera:
{
- SaveOriginalCamera();
- StartStationaryCamera();
+ floatingKrakenAdjustment = -CTKrakensbane.FloatingOriginOffset;
+ if (!inOrbit)
+ floatingKrakenAdjustment += (vessel.srf_velocity - CTKrakensbane.FrameVelocity) * TimeWarp.fixedDeltaTime;
+ else if (!inHighWarp && useObtVel != wasUsingObtVel) // Only needed when crossing the boundary.
+ floatingKrakenAdjustment += ((useObtVel ? vessel.obt_velocity : vessel.srf_velocity) - CTKrakensbane.FrameVelocity) * TimeWarp.fixedDeltaTime;
+ if (hasDied) deathCamPosition += floatingKrakenAdjustment;
+ else
+ {
+ cameraParent.transform.position += floatingKrakenAdjustment;
+ dogfightRotationTarget += floatingKrakenAdjustment;
+ }
+ // if (DEBUG2 && !GameIsPaused)
+ // {
+ // var cmb = FlightGlobals.currentMainBody;
+ // Debug2Log("situation: " + vessel.situation);
+ // Debug2Log("warp mode: " + TimeWarp.WarpMode + ", warp factor: " + TimeWarp.CurrentRate);
+ // Debug2Log($"radius: {cmb.Radius}, radiusAtmoFactor: {cmb.radiusAtmoFactor}, atmo: {cmb.atmosphere}, atmoDepth: {cmb.atmosphereDepth}");
+ // Debug2Log("speed: " + vessel.Speed().ToString("G3") + ", vel: " + vessel.Velocity().ToString("G3"));
+ // Debug2Log("offset from vessel CoM: " + (flightCamera.transform.position - vessel.CoM).ToString("G3"));
+ // Debug2Log("camParentPos - flightCamPos: " + (cameraParent.transform.position - flightCamera.transform.position).ToString("G3"));
+ // Debug2Log($"inOrbit: {inOrbit}, inHighWarp: {inHighWarp}, useObtVel: {useObtVel}");
+ // Debug2Log($"altitude: {vessel.altitude}");
+ // Debug2Log("vessel velocity: " + vessel.Velocity().ToString("G3") + ", Kraken velocity: " + Krakensbane.GetFrameVelocity().ToString("G3"));
+ // Debug2Log("(vv - kv): " + (vessel.Velocity() - Krakensbane.GetFrameVelocity()).ToString("G3") + ", ΔKv: " + Krakensbane.GetLastCorrection().ToString("G3"));
+ // Debug2Log("(vv - kv)*Δt: " + ((vessel.Velocity() - Krakensbane.GetFrameVelocity()) * TimeWarp.fixedDeltaTime).ToString("G3"));
+ // Debug2Log("(sv - kv)*Δt: " + ((vessel.srf_velocity - Krakensbane.GetFrameVelocity()) * TimeWarp.fixedDeltaTime).ToString("G3"));
+ // Debug2Log("floating origin offset: " + FloatingOrigin.Offset.ToString("G3") + ", offsetNonKB: " + FloatingOrigin.OffsetNonKrakensbane.ToString("G3"));
+ // Debug2Log($"ΔKv*Δt: {(Krakensbane.GetLastCorrection() * TimeWarp.fixedDeltaTime).ToString("G3")}");
+ // Debug2Log($"onKb - kv*Δt: {(FloatingOrigin.OffsetNonKrakensbane - Krakensbane.GetFrameVelocity() * TimeWarp.fixedDeltaTime).ToString("G3")}");
+ // Debug2Log("floatingKrakenAdjustment: " + floatingKrakenAdjustment.ToString("G3"));
+ // }
+ break;
}
- else
+ case ToolModes.StationaryCamera:
{
- //RevertCamera();
- StartStationaryCamera();
+ if (maintainInitialVelocity && !randomMode && !autoLandingCamEnabled) // Don't maintain velocity when using random mode or auto landing camera.
+ {
+ if (useOrbital && initialOrbit != null)
+ {
+ // Situations: {high warp, low warp, normal} x {inOrbit && >100km, inOrbit && <100km, !inOrbit}
+ lastVesselCoM += initialOrbit.getOrbitalVelocityAtUT(Planetarium.GetUniversalTime() + ((inOrbit && !inHighWarp) ? -0.5f : 0) * TimeWarp.fixedDeltaTime).xzy * TimeWarp.fixedDeltaTime;
+ }
+ else
+ { lastVesselCoM += initialVelocity * TimeWarp.fixedDeltaTime; }
+
+ if (inHighWarp) // This exactly corrects for motion when >100km and is correct up to floating precision for <100km.
+ {
+ floatingKrakenAdjustment = -(useObtVel ? vessel.obt_velocity : vessel.srf_velocity) * TimeWarp.fixedDeltaTime;
+ lastVesselCoM += floatingKrakenAdjustment;
+ lastCamParentPosition += floatingKrakenAdjustment;
+ }
+ else if (wasInHighWarp)
+ {
+ if (vessel.altitude > 1e5) // This is correct for >100km.
+ {
+ floatingKrakenAdjustment = -floatingKrakenAdjustment - (useObtVel ? vessel.obt_velocity : vessel.srf_velocity) * TimeWarp.fixedDeltaTime; // Correction reverts the previous correction and adjusts for current velocity.
+ lastVesselCoM += floatingKrakenAdjustment;
+ lastCamParentPosition += floatingKrakenAdjustment;
+ }
+ else
+ {
+ floatingKrakenAdjustment = (previousWarpFactor * vessel.srf_velocity - vessel.obt_velocity) * TimeWarp.fixedDeltaTime;
+ lastVesselCoM += floatingKrakenAdjustment;
+ lastCamParentPosition += floatingKrakenAdjustment;
+ }
+ }
+ else
+ {
+ if (CTKrakensbane.IsActive)
+ {
+ if (vessel.altitude > 1e5)
+ floatingKrakenAdjustment = -CTKrakensbane.FloatingOriginOffsetNonKrakensbane;
+ else if (wasAbove1e5)
+ floatingKrakenAdjustment = -CTKrakensbane.FloatingOriginOffsetNonKrakensbane + (vessel.srf_velocity - CTKrakensbane.FrameVelocity) * TimeWarp.fixedDeltaTime;
+ else if (inOrbit)
+ floatingKrakenAdjustment = -vessel.obt_velocity * TimeWarp.fixedDeltaTime - CTKrakensbane.FloatingOriginOffset;
+ else
+ floatingKrakenAdjustment = -vessel.srf_velocity * TimeWarp.fixedDeltaTime - CTKrakensbane.FloatingOriginOffset;
+ lastVesselCoM += floatingKrakenAdjustment;
+ lastCamParentPosition += floatingKrakenAdjustment;
+ }
+ }
+ }
+ else
+ {
+ if (CTKrakensbane.IsActive)
+ {
+ floatingKrakenAdjustment = -CTKrakensbane.FloatingOriginOffsetNonKrakensbane;
+ lastVesselCoM += floatingKrakenAdjustment;
+ lastCamParentPosition += floatingKrakenAdjustment;
+ }
+ }
+ break;
}
+ }
+ if (DEBUG && vessel.situation != lastVesselSituation)
+ {
+ DebugLog($"Vessel Situation changed from {lastVesselSituation} to {vessel.situation}");
+ lastVesselSituation = vessel.situation;
+ }
+ if (DEBUG && TimeWarp.WarpMode == TimeWarp.Modes.LOW && CTKrakensbane.FloatingOriginOffset.sqrMagnitude > 10)
+ {
+ DebugLog("Floating origin offset: " + CTKrakensbane.FloatingOriginOffset.ToString("0.0") + ", Krakensbane velocity correction: " + Krakensbane.GetLastCorrection().ToString("0.0"));
+ }
+ // #if DEBUG
+ // if (DEBUG && (flightCamera.transform.position - (vessel.CoM - lastVesselCoM) - lastCameraPosition).sqrMagnitude > 1)
+ // {
+ // DebugLog("situation: " + vessel.situation + " inOrbit " + inOrbit + " useObtVel " + useObtVel);
+ // DebugLog("warp mode: " + TimeWarp.WarpMode + ", fixedDeltaTime: " + TimeWarp.fixedDeltaTime + ", was: " + previousWarpFactor);
+ // DebugLog($"high warp: {inHighWarp} | {wasInHighWarp}");
+ // DebugLog($">100km: {vessel.altitude > 1e5} | {wasAbove1e5} ({vessel.altitude.ToString("G8")})");
+ // DebugLog("floating origin offset: " + FloatingOrigin.Offset.ToString("G6"));
+ // DebugLog("KB frame vel: " + Krakensbane.GetFrameVelocity().ToString("G6"));
+ // DebugLog("offsetNonKB: " + FloatingOrigin.OffsetNonKrakensbane.ToString("G6"));
+ // DebugLog("vv*Δt: " + (vessel.obt_velocity * TimeWarp.fixedDeltaTime).ToString("G6"));
+ // DebugLog("sv*Δt: " + (vessel.srf_velocity * TimeWarp.fixedDeltaTime).ToString("G6"));
+ // DebugLog("kv*Δt: " + (Krakensbane.GetFrameVelocity() * TimeWarp.fixedDeltaTime).ToString("G6"));
+ // DebugLog("ΔKv: " + Krakensbane.GetLastCorrection().ToString("G6"));
+ // DebugLog("(sv-kv)*Δt" + ((vessel.srf_velocity - Krakensbane.GetFrameVelocity()) * TimeWarp.fixedDeltaTime).ToString("G6"));
+ // DebugLog("floatingKrakenAdjustment: " + floatingKrakenAdjustment.ToString("G6"));
+ // DebugLog("Camera pos: " + (flightCamera.transform.position - (vessel.CoM - lastVesselCoM)).ToString("G6"));
+ // DebugLog("ΔCamera: " + (flightCamera.transform.position - (vessel.CoM - lastVesselCoM) - lastCameraPosition).ToString("G6"));
+
+ // }
+ // #endif
+ wasUsingObtVel = useObtVel;
+ wasAbove1e5 = vessel.altitude > 1e5;
+ wasInHighWarp = inHighWarp;
+ previousWarpFactor = TimeWarp.CurrentRate;
+ }
+ }
+
+ void Update()
+ {
+ if (!isRecordingInput && !boundThisFrame)
+ {
+ if (GetKeyPress(toggleMenu))
+ {
+ ToggleGui();
+ }
+
+ if (GetKeyPress(revertKey))
+ {
+ autoEnableOverriden = true;
+ RevertCamera();
+ }
+ else if (GetKeyPress(cameraKey))
+ {
+ autoEnableOverriden = false;
+ if (!cameraToolActive && randomMode)
+ {
+ ChooseRandomMode();
}
- else if(toolMode == ToolModes.DogfightCamera)
+ CameraActivate();
+ }
+
+ if (GetKeyPress(fmModeToggleKey))
+ {
+ if (!textInput)
{
- if(!cameraToolActive)
- {
- SaveOriginalCamera();
- StartDogfightCamera();
- }
- else
- {
- StartDogfightCamera();
- }
+ // Cycle through the free move modes.
+ var fmModes = (FMModeTypes[])Enum.GetValues(typeof(FMModeTypes));
+ var fmModeIndex = (fmModes.IndexOf(fmMode) + 1) % fmModes.Length;
+ fmMode = fmModes[fmModeIndex];
+ fmSpeeds = Vector4.zero;
+ if (DEBUG) DebugLog($"Switching to free move mode {fmMode}");
}
- else if(toolMode == ToolModes.Pathing)
+ else
{
- if(!cameraToolActive)
- {
- SaveOriginalCamera();
- }
- StartPathingCam();
- PlayPathingCam();
+ if (DEBUG) DebugLog($"Unable to switch to free move mode {FMModeTypes.Speed} while in numeric input mode.");
}
}
-
-
+ if (GetKeyPress(fmPivotModeKey))
+ {
+ var fmPivotModes = (FMPivotModes[])Enum.GetValues(typeof(FMPivotModes));
+ var fmPivotModeIndex = (fmPivotModes.IndexOf(fmPivotMode) + 1) % fmPivotModes.Length;
+ fmPivotMode = fmPivotModes[fmPivotModeIndex];
+ if (DEBUG) DebugLog($"Switching to pivot mode {fmPivotMode}");
+ }
}
- if(Input.GetMouseButtonUp(0))
+ if (MapView.MapIsEnabled) return; // Don't do anything else in map mode.
+
+ if (Input.GetMouseButtonUp(0))
{
mouseUp = true;
}
-
-
+
//get target transform from mouseClick
- if(waitingForTarget && mouseUp && Input.GetKeyDown(KeyCode.Mouse0))
+ if (waitingForTarget && mouseUp && Input.GetKeyDown(KeyCode.Mouse0))
{
Part tgt = GetPartFromMouse();
- if(tgt!=null)
+ if (toolMode == ToolModes.DogfightCamera)
{
- camTarget = tgt;
- hasTarget = true;
+ if (tgt != null && tgt.vessel == vessel) camTarget = tgt;
+ else camTarget = null;
+ chasePlaneTargetIsEVA = camTarget != null ? camTarget.IsKerbalEVA() : vessel.isEVA;
}
- else
+ else // Stationary
{
- Vector3 pos = GetPosFromMouse();
- if(pos != Vector3.zero)
+ if (tgt != null)
{
- lastTargetPosition = pos;
+ camTarget = tgt;
hasTarget = true;
}
+ else
+ {
+ Vector3 pos = GetPosFromMouse();
+ if (pos != Vector3.zero)
+ {
+ lastTargetPosition = pos;
+ hasTarget = true;
+ }
+ }
}
-
waitingForTarget = false;
}
-
+
//set position from mouseClick
- if(waitingForPosition && mouseUp && Input.GetKeyDown(KeyCode.Mouse0))
+ if (waitingForPosition && mouseUp && Input.GetKeyDown(KeyCode.Mouse0))
{
Vector3 pos = GetPosFromMouse();
- if(pos!=Vector3.zero)// && isStationaryCamera)
+ if (pos != Vector3.zero)// && isStationaryCamera)
{
presetOffset = pos;
setPresetOffset = true;
}
- else Debug.Log ("No pos from mouse click");
-
+ else Debug.Log("[CameraTools]: No pos from mouse click");
+
waitingForPosition = false;
}
-
-
-
- }
- public void ShakeCamera(float magnitude)
- {
- shakeMagnitude = Mathf.Max(shakeMagnitude, magnitude);
+ if (BDArmory.IsInhibited) return; // Don't do anything else while BDA is inhibiting us.
+ if (cameraToolActive)
+ {
+ if (!hasDied)
+ {
+ switch (toolMode)
+ {
+ case ToolModes.StationaryCamera:
+ UpdateStationaryCamera();
+ break;
+ case ToolModes.Pathing:
+ if (useRealTime)
+ UpdatePathingCam();
+ break;
+ case ToolModes.DogfightCamera: // Dogfight mode is mostly handled in FixedUpdate due to relying on interpolation of positions updated in the physics update.
+ break;
+ default:
+ break;
+ }
+ }
+ if (enableVFX) origParent.position = cameraParent.transform.position; // KSP's aero FX are only enabled when close to the origParent's position.
+ }
}
-
-
- int posCounter = 0;//debug
+
void FixedUpdate()
{
- if(!FlightGlobals.ready)
+ // Note: we have to perform several of the camera adjustments during FixedUpdate to avoid jitter in the Lerps in the camera position and rotation due to inconsistent numbers of physics updates per frame.
+ if (!FlightGlobals.ready || GameIsPaused) return;
+ if (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Flight) return;
+ if (MapView.MapIsEnabled) return; // Don't do anything in map mode.
+ if (BDArmory.IsInhibited) return; // Don't do anything while BDA is inhibiting us.
+
+ if (DEBUG2 && !GameIsPaused) debug2Messages.Clear();
+
+ if (cameraToolActive)
{
- return;
+ if (!hasDied && flightCamera.transform.parent != cameraParent.transform)
+ {
+ if (flightCamera.transform.parent == origParent)
+ {
+ message = "Camera parent got reverted to the main camera parent! Stealing it back!";
+ Debug.Log("[CameraTools]: " + message);
+ if (DEBUG) DebugLog(message);
+ flightCamera.transform.parent = cameraParent.transform; // KSP reverted the camera parent (e.g., when spawning a new missile or kerbal), steal it back.
+ }
+ else
+ {
+ message = $"Someone has stolen the camera parent ({flightCamera.transform.parent.name} vs {cameraParent.transform.name})! Abort!";
+ Debug.Log("[CameraTools]: " + message);
+ if (DEBUG) DebugLog(message);
+ cameraToolActive = false;
+ cameraParentWasStolen = true;
+ RevertCamera();
+ }
+ }
+ else if (hasDied)
+ {
+ deathCamVelocity = (deathCamVelocity - deathCamTargetVelocity) * deathCamDecayFactor + deathCamTargetVelocity; // Slow down to the target velocity.
+ deathCamPosition += deathCamVelocity * TimeWarp.fixedDeltaTime;
+ if (CTKrakensbane.IsActive) deathCamPosition -= CTKrakensbane.FloatingOriginOffsetNonKrakensbane;
+ if (toolMode == ToolModes.DogfightCamera && deathCamTarget && deathCamTarget.gameObject.activeInHierarchy)
+ {
+ var deathCamTargetPosition = deathCamTarget.transform.position + TimeWarp.fixedDeltaTime * deathCamTarget.rb_velocity;
+ var targetRotation = Quaternion.LookRotation(deathCamTargetPosition - deathCamPosition, cameraUp);
+ var lerpFactor = Mathf.Clamp01(Quaternion.Angle(deathCamRotation, targetRotation) / 45); // Slow down rotation once with 45°.
+ deathCamRotation = Quaternion.Slerp(deathCamRotation, targetRotation, lerpFactor * dogfightLerp);
+ }
+ deathCam.transform.SetPositionAndRotation(deathCamPosition, deathCamRotation);
+ return; // Do nothing else until we have an active vessel.
+ }
}
- if(FlightGlobals.ActiveVessel != null && (vessel==null || vessel!=FlightGlobals.ActiveVessel))
+ if (vessel == null || vessel != FlightGlobals.ActiveVessel)
{
vessel = FlightGlobals.ActiveVessel;
}
-
- if(vessel != null)
+
+ if (!autoEnableOverriden && bdArmory.autoEnableForBDA && (toolMode != ToolModes.Pathing || (selectedPathIndex >= 0 && currentPath.keyframeCount > 0)))
{
- lastVesselPosition = vessel.transform.position;
+ bdArmory.AutoEnableForBDA();
}
-
-
- //stationary camera
- if(cameraToolActive)
+ if (cameraToolActive)
{
- if(toolMode == ToolModes.StationaryCamera)
+ switch (toolMode)
{
- UpdateStationaryCamera();
- }
- else if(toolMode == ToolModes.DogfightCamera)
- {
- UpdateDogfightCamera();
- }
- else if(toolMode == ToolModes.Pathing)
- {
- UpdatePathingCam();
+ case ToolModes.DogfightCamera:
+ UpdateDogfightCamera();
+ if (dogfightTarget && dogfightTarget.isActiveVessel)
+ {
+ dogfightTarget = null;
+ }
+ if (fmMode == FMModeTypes.Speed)
+ {
+ dogfightOffsetY = Mathf.Clamp(dogfightOffsetY + fmSpeeds.y, -dogfightMaxOffset, dogfightMaxOffset);
+ if (Mathf.Abs(dogfightOffsetY) >= dogfightMaxOffset) fmSpeeds.y = 0;
+ dogfightOffsetX = Mathf.Clamp(dogfightOffsetX + fmSpeeds.x, -dogfightMaxOffset, dogfightMaxOffset);
+ if (Mathf.Abs(dogfightOffsetX) >= dogfightMaxOffset) fmSpeeds.x = 0;
+ dogfightDistance = Mathf.Clamp(dogfightDistance + fmSpeeds.z, 1f, dogfightMaxDistance);
+ if (dogfightDistance <= 1f || dogfightDistance >= dogfightMaxDistance) fmSpeeds.z = 0;
+ if (!autoFOV)
+ {
+ zoomExp = Mathf.Clamp(zoomExp + fmSpeeds.w, 1, zoomMaxExp);
+ if (zoomExp <= 1 || zoomExp >= zoomMaxExp) fmSpeeds.w = 0;
+ }
+ else
+ {
+ autoZoomMargin = Mathf.Clamp(autoZoomMargin + 10 * fmSpeeds.w, 0, autoZoomMarginMax);
+ if (autoZoomMargin <= 0 || autoZoomMargin >= autoZoomMarginMax) fmSpeeds.w = 0;
+ }
+ }
+ break;
+ case ToolModes.StationaryCamera:
+ // Updating of the stationary camera is handled in Update.
+ if (fmMode == FMModeTypes.Speed)
+ {
+ manualPosition += upAxis * fmSpeeds.y + forwardAxis * fmSpeeds.z + rightAxis * fmSpeeds.x;
+ if (!autoFOV)
+ {
+ zoomExp = Mathf.Clamp(zoomExp + fmSpeeds.w, 1f, zoomMaxExp);
+ if (zoomExp <= 1f || zoomExp >= zoomMaxExp) fmSpeeds.w = 0;
+ }
+ else
+ {
+ autoZoomMargin = Mathf.Clamp(autoZoomMargin + 10 * fmSpeeds.w, 0f, autoZoomMarginMax);
+ if (autoZoomMargin <= 0f || autoZoomMargin >= autoZoomMarginMax) fmSpeeds.w = 0;
+ }
+ }
+ break;
+ case ToolModes.Pathing:
+ if (CTKrakensbane.IsActive && currentPath.isGeoSpatial) cameraParent.transform.position -= CTKrakensbane.FloatingOriginOffsetNonKrakensbane;
+ if (!useRealTime) UpdatePathingCam();
+ if (fmMode == FMModeTypes.Speed)
+ {
+ flightCamera.transform.position += upAxis * fmSpeeds.y + forwardAxis * fmSpeeds.z + rightAxis * fmSpeeds.x; // Note: for vessel relative movement, the modifier key will need to be held.
+ zoomExp = Mathf.Clamp(zoomExp + fmSpeeds.w, 1f, zoomMaxExp);
+ if (zoomExp <= 1f || zoomExp >= zoomMaxExp) fmSpeeds.w = 0;
+ }
+ break;
+ default:
+ break;
}
}
else
{
- if(!autoFOV)
+ if (!autoFOV)
{
- zoomFactor = Mathf.Exp(zoomExp)/Mathf.Exp(1);
+ zoomFactor = Mathf.Exp(zoomExp) / Mathf.Exp(1);
}
}
+ }
- if(toolMode == ToolModes.DogfightCamera)
+ void LateUpdate()
+ {
+ boundThisFrame = false;
+ UpdateCameraShake(); // Update camera shake each frame so that it dies down.
+ if (BDArmory.IsInhibited) return; // Don't do anything else while BDA is inhibiting us.
+ if (hasDied && cameraToolActive)
{
- if(dogfightTarget && dogfightTarget.isActiveVessel)
+ if (flightCamera.transform.parent != deathCam.transform) // Something else keeps trying to steal the camera after the vessel has died, so we need to keep overriding it.
{
- dogfightTarget = null;
- if(cameraToolActive)
- {
- RevertCamera();
- }
+ SetDeathCam();
+ }
+ }
+ else if (!vesselSwitched)
+ {
+ switch (CameraManager.Instance.currentCameraMode)
+ {
+ case CameraManager.CameraMode.IVA:
+ var IVACamera = CameraManager.GetCurrentCamera();
+ deathCamPosition = IVACamera.transform.position;
+ deathCamRotation = IVACamera.transform.rotation;
+ break;
+ case CameraManager.CameraMode.Flight:
+ deathCamPosition = flightCamera.transform.position;
+ deathCamRotation = flightCamera.transform.rotation;
+ break;
}
}
-
-
- if(hasDied && Time.time-diedTime > 2)
+ if (cameraToolActive && vesselSwitched) // We perform this here instead of waiting for the next frame to avoid a flicker of the camera being switched during a FixedUpdate.
{
- RevertCamera();
+ vesselSwitched = false;
+ toolMode = switchToMode;
+ flightCamera.transform.position = deathCamPosition; // Revert flight camera changes that KSP makes using the deathCam's last values.
+ flightCamera.transform.rotation = deathCamRotation;
+ CameraActivate();
}
}
- void StartDogfightCamera()
+ public void CameraActivate()
{
- if(FlightGlobals.ActiveVessel == null)
+ if (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Flight)
{
- Debug.Log("No active vessel.");
- return;
+ activateWhenInFlightMode = true;
+ revertWhenInFlightMode = false;
+ return; // Don't activate if we're not in Flight mode.
}
-
-
-
- if(!dogfightTarget)
+ activateWhenInFlightMode = false;
+ if (DEBUG)
{
- dogfightVelocityChase = true;
+ message = $"Activating camera for mode {toolMode}. Currently {(cameraToolActive ? "active" : "inactive")}.";
+ Debug.Log($"[CameraTools]: {message}"); DebugLog(message);
}
- else
+ if (!cameraToolActive)
{
- dogfightVelocityChase = false;
+ timeControl.SetTimeControlCameraZoomFix(false);
+ betterTimeWarp.SetBetterTimeWarpScaleCameraSpeed(false);
+ freeLook = false;
+ freeLookStartUpDistance = Vector2.zero;
}
-
- dogfightPrevTarget = dogfightTarget;
-
- hasDied = false;
- vessel = FlightGlobals.ActiveVessel;
- cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.CoM).normalized;
-
- flightCamera.SetTargetNone();
- flightCamera.transform.parent = cameraParent.transform;
- flightCamera.DeactivateUpdate();
- cameraParent.transform.position = vessel.transform.position+vessel.rb_velocity*Time.fixedDeltaTime;
-
- cameraToolActive = true;
-
- ResetDoppler();
- if(OnResetCTools != null)
+ if (!cameraToolActive && !cameraParentWasStolen)
{
- OnResetCTools();
+ SaveOriginalCamera();
}
-
- SetDoppler(false);
- AddAtmoAudioControllers(false);
- }
-
- void UpdateDogfightCamera()
- {
- if(!vessel || (!dogfightTarget && !dogfightLastTarget && !dogfightVelocityChase))
+ UpdateDeathCamFromFlight();
+ if (toolMode == ToolModes.StationaryCamera)
{
- RevertCamera();
+ StartStationaryCamera();
+ }
+ else if (toolMode == ToolModes.DogfightCamera)
+ {
+ StartDogfightCamera();
+ }
+ else if (toolMode == ToolModes.Pathing)
+ {
+ StartPathingCam();
+ PlayPathingCam();
+ }
+ }
+
+ #region Dogfight Camera
+ void StartDogfightCamera()
+ {
+ toolMode = ToolModes.DogfightCamera;
+ vessel = FlightGlobals.ActiveVessel;
+ if (vessel == null)
+ {
+ Debug.Log("[CameraTools]: No active vessel.");
return;
}
-
+ if (DEBUG)
+ {
+ message = $"Starting dogfight camera{(cockpitView ? " with cockpit view" : "")} for {vessel.vesselName}{(dogfightTarget ? $" vs {dogfightTarget.vesselName}" : "")}.";
+ Debug.Log($"[CameraTools]: {message}");
+ DebugLog(message);
+ }
- if(dogfightTarget)
+ if (MouseAimFlight.IsMouseAimActive)
{
+ dogfightTarget = null;
dogfightLastTarget = true;
- dogfightLastTargetPosition = dogfightTarget.CoM;
- dogfightLastTargetVelocity = dogfightTarget.rb_velocity;
+ dogfightVelocityChase = false;
}
- else if(dogfightLastTarget)
+ else if (dogfightChasePlaneMode)
{
- dogfightLastTargetPosition += dogfightLastTargetVelocity * Time.fixedDeltaTime;
+ dogfightVelocityChase = true; // Fall back to velocity chase if chase plane mode gets disabled while the camera is active.
}
-
- cameraParent.transform.position = (vessel.CoM - (vessel.rb_velocity * Time.fixedDeltaTime));
-
- if(dogfightVelocityChase)
+ else if (BDArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 1)
{
- if(vessel.srfSpeed > 1)
- {
- dogfightLastTargetPosition = vessel.CoM + (vessel.srf_velocity.normalized * 5000);
- }
- else
- {
- dogfightLastTargetPosition = vessel.CoM + (vessel.ReferenceTransform.up * 5000);
- }
+ dogfightLastTarget = true;
+ dogfightVelocityChase = false;
}
-
- Vector3 offsetDirection = Vector3.Cross(cameraUp, dogfightLastTargetPosition - vessel.CoM).normalized;
- Vector3 camPos = vessel.CoM + ((vessel.CoM - dogfightLastTargetPosition).normalized * dogfightDistance) + (dogfightOffsetX * offsetDirection) + (dogfightOffsetY * cameraUp);
-
- Vector3 localCamPos = cameraParent.transform.InverseTransformPoint(camPos);
- flightCamera.transform.localPosition = Vector3.Lerp(flightCamera.transform.localPosition, localCamPos, dogfightLerp * Time.fixedDeltaTime);
-
- //rotation
- Quaternion vesselLook = Quaternion.LookRotation(vessel.CoM-flightCamera.transform.position, cameraUp);
- Quaternion targetLook = Quaternion.LookRotation(dogfightLastTargetPosition-flightCamera.transform.position, cameraUp);
- Quaternion camRot = Quaternion.Lerp(vesselLook, targetLook, 0.5f);
- flightCamera.transform.rotation = Quaternion.Lerp(flightCamera.transform.rotation, camRot, dogfightLerp * Time.fixedDeltaTime);
-
- //autoFov
- if(autoFOV)
+ else if (dogfightTarget)
{
- float targetFoV;
- if(dogfightVelocityChase)
- {
- targetFoV = Mathf.Clamp((7000 / (dogfightDistance + 100)) - 14 + autoZoomMargin, 2, 60);
- }
- else
- {
- float angle = Vector3.Angle((dogfightLastTargetPosition + (dogfightLastTargetVelocity * Time.fixedDeltaTime)) - flightCamera.transform.position, (vessel.CoM + (vessel.rb_velocity * Time.fixedDeltaTime)) - flightCamera.transform.position);
- targetFoV = Mathf.Clamp(angle + autoZoomMargin, 0.1f, 60f);
- }
- manualFOV = targetFoV;
+ dogfightVelocityChase = false;
}
- //FOV
- if(!autoFOV)
+ else if (BDArmory.hasBDA && bdArmory.isRunningWaypoints)
{
- zoomFactor = Mathf.Exp(zoomExp) / Mathf.Exp(1);
- manualFOV = 60 / zoomFactor;
- updateFOV = (currentFOV != manualFOV);
- if(updateFOV)
- {
- currentFOV = Mathf.Lerp(currentFOV, manualFOV, 0.1f);
- flightCamera.SetFoV(currentFOV);
- updateFOV = false;
- }
+ dogfightVelocityChase = true;
}
- else
+ else if (BDArmory.hasBDA && bdArmory.isBDMissile)
{
- currentFOV = Mathf.Lerp(currentFOV, manualFOV, 0.1f);
- flightCamera.SetFoV(currentFOV);
- zoomFactor = 60 / currentFOV;
+ dogfightLastTarget = true;
+ dogfightVelocityChase = false;
}
-
- //free move
- if(enableKeypad)
+ else
{
- if(Input.GetKey(fmUpKey))
- {
- dogfightOffsetY += freeMoveSpeed * Time.fixedDeltaTime;
- dogfightOffsetY = Mathf.Clamp(dogfightOffsetY, -dogfightMaxOffset, dogfightMaxOffset);
- }
- else if(Input.GetKey(fmDownKey))
- {
- dogfightOffsetY -= freeMoveSpeed * Time.fixedDeltaTime;
- dogfightOffsetY = Mathf.Clamp(dogfightOffsetY, -dogfightMaxOffset, dogfightMaxOffset);
- }
- if(Input.GetKey(fmForwardKey))
- {
- dogfightDistance -= freeMoveSpeed * Time.fixedDeltaTime;
- dogfightDistance = Mathf.Clamp(dogfightDistance, 1f, 100f);
- }
- else if(Input.GetKey(fmBackKey))
- {
- dogfightDistance += freeMoveSpeed * Time.fixedDeltaTime;
- dogfightDistance = Mathf.Clamp(dogfightDistance, 1f, 100f);
- }
- if(Input.GetKey(fmLeftKey))
- {
- dogfightOffsetX -= freeMoveSpeed * Time.fixedDeltaTime;
- dogfightOffsetX = Mathf.Clamp(dogfightOffsetX, -dogfightMaxOffset, dogfightMaxOffset);
- }
- else if(Input.GetKey(fmRightKey))
- {
- dogfightOffsetX += freeMoveSpeed * Time.fixedDeltaTime;
- dogfightOffsetX = Mathf.Clamp(dogfightOffsetX, -dogfightMaxOffset, dogfightMaxOffset);
- }
-
- //keyZoom
- if(!autoFOV)
+ if (false && randomMode && rng.Next(3) == 0)
{
- if(Input.GetKey(fmZoomInKey))
- {
- zoomExp = Mathf.Clamp(zoomExp + (keyZoomSpeed * Time.fixedDeltaTime), 1, 8);
- }
- else if(Input.GetKey(fmZoomOutKey))
- {
- zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, 8);
- }
+ dogfightVelocityChase = false; // sometimes throw in a non chase angle
}
else
{
- if(Input.GetKey(fmZoomInKey))
- {
- autoZoomMargin = Mathf.Clamp(autoZoomMargin + (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, 50);
- }
- else if(Input.GetKey(fmZoomOutKey))
- {
- autoZoomMargin = Mathf.Clamp(autoZoomMargin - (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, 50);
- }
+ dogfightVelocityChase = true;
}
}
- //vessel camera shake
- if(shakeMultiplier > 0)
+ if (dogfightInertialChaseMode && dogfightInertialFactor > 0)
{
- foreach(var v in FlightGlobals.Vessels)
- {
- if(!v || !v.loaded || v.packed || v.isActiveVessel) continue;
- VesselCameraShake(v);
- }
+ dogfightLerpMomentum = default;
+ dogfightLerpDelta = default;
+ dogfightRotationTarget = vessel != null ? vessel.CoM : default;
}
- UpdateCameraShake();
- if(hasBDAI && useBDAutoTarget)
- {
- Vessel newAITarget = GetAITargetedVessel();
- if(newAITarget)
- {
- dogfightTarget = newAITarget;
- }
- }
+ hasDied = false;
+ cameraUp = vessel.up;
- if(dogfightTarget != dogfightPrevTarget)
- {
- //RevertCamera();
- StartDogfightCamera();
- }
+ SetCameraParent(deathCam.transform, true); // First update the cameraParent to the last deathCam configuration offset for the active vessel's CoM.
+
+ cameraToolActive = true;
+
+ ResetDoppler();
+ if (OnResetCTools != null)
+ { OnResetCTools(); }
+ SetDoppler(false);
+ AddAtmoAudioControllers(false);
}
- void UpdateStationaryCamera()
+ void UpdateDogfightCamera()
{
- if(useAudioEffects)
+ if (!vessel || (!dogfightTarget && !dogfightLastTarget && !dogfightVelocityChase && !dogfightChasePlaneMode))
{
- speedOfSound = 233 * Math.Sqrt(1 + (FlightGlobals.getExternalTemperature(vessel.GetWorldPos3D(), vessel.mainBody) / 273.15));
- //Debug.Log("speed of sound: " + speedOfSound);
+ if (DEBUG) { Debug.Log("[CameraTools]: Reverting during UpdateDogfightCamera"); }
+ RevertCamera();
+ return;
}
- if(posCounter < 3)
+ if (cockpitView)
{
- posCounter++;
- Debug.Log("flightCamera position: " + flightCamera.transform.position);
- flightCamera.transform.position = resetPositionFix;
- if(hasSavedRotation)
+ if (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA) // Already enabled, do nothing.
+ { return; }
+ // Check that there's still a kerbal to switch to.
+ if (cockpits.Any(cockpit => cockpit != null && cockpit.part != null && cockpit.part.protoModuleCrew.Count > 0))
{
- flightCamera.transform.rotation = savedRotation;
+ try
+ {
+ CameraManager.Instance.SetCameraIVA(); // Try to enable IVA camera.
+ }
+ catch (Exception e)
+ {
+ Debug.LogWarning($"[CameraTools.CamTools]: Exception thrown trying to set IVA camera mode, aborting. {e.Message}");
+ cockpitView = false;
+ }
}
+ else
+ cockpitView = false;
+ if (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA) // Success!
+ { return; }
}
- if(flightCamera.Target != null) flightCamera.SetTargetNone(); //dont go to next vessel if vessel is destroyed
- if (camTarget != null)
+ var vesselTransform = vessel.ReferenceTransform != null ? vessel.ReferenceTransform : vessel.vesselTransform; // Use the reference transform, but fall back to the regular vesselTransform if it's null.
+ var cameraTransform = flightCamera.transform;
+ if (MouseAimFlight.IsMouseAimActive)
+ { // We need to set these each time as MouseAimFlight can be enabled/disabled while CameraTools is active.
+ dogfightTarget = null;
+ dogfightLastTarget = true;
+ dogfightVelocityChase = false;
+ dogfightLastTargetVelocity = Vector3.zero;
+ mouseAimFlightTarget = MouseAimFlight.GetMouseAimTarget;
+ mouseAimFlightTargetLocal = cameraTransform.InverseTransformDirection(mouseAimFlightTarget);
+ dogfightLastTargetPosition = (mouseAimFlightTarget.normalized + vessel.srf_vel_direction) * 5000f + vessel.CoM;
+ }
+ else if (dogfightChasePlaneMode)
{
- Vector3 lookPosition = camTarget.transform.position;
- if(targetCoM)
+ dogfightLastTargetPosition = camTarget == null ? vessel.CoM : camTarget.transform.position;
+ }
+ else if (BDArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 1)
+ {
+ dogfightLastTarget = true;
+ dogfightLastTargetVelocity = Vector3.zero;
+ dogfightLastTargetPosition = bdArmory.GetCentroid();
+ // if (DEBUG2 && !GameIsPaused) Debug2Log($"Centroid: {dogfightLastTargetPosition:G3}");
+ }
+ else if (dogfightTarget)
+ {
+ if (loadedVessels == null) UpdateLoadedVessels();
+ dogfightLastTarget = true;
+ dogfightLastTargetPosition = dogfightTarget.CoM;
+ dogfightLastTargetVelocity = dogfightTarget.Velocity();
+ dogfightVelocityChase = false;
+ }
+ else if (dogfightLastTarget)
+ {
+ if (BDArmory.hasBDA && bdArmory.isBDMissile)
{
- lookPosition = camTarget.vessel.CoM;
+ var missileTargetPosition = bdArmory.GetMissileTargetedPosition();
+ if (missileTargetPosition == default) // If there's no target position, just do a velocity chase.
+ {
+ dogfightLastTarget = false;
+ dogfightVelocityChase = true;
+ }
+ else { dogfightLastTargetPosition = missileTargetPosition; }
}
-
- lookPosition += 2*camTarget.vessel.rb_velocity * Time.fixedDeltaTime;
- if(targetCoM)
+ else
{
- lookPosition += camTarget.vessel.rb_velocity * Time.fixedDeltaTime;
+ if (CTKrakensbane.IsActive)
+ { dogfightLastTargetPosition -= CTKrakensbane.FloatingOriginOffsetNonKrakensbane; }
+ dogfightLastTargetPosition += dogfightLastTargetVelocity * TimeWarp.fixedDeltaTime;
}
-
- flightCamera.transform.rotation = Quaternion.LookRotation(lookPosition - flightCamera.transform.position, cameraUp);
- lastTargetPosition = lookPosition;
- }
- else if(hasTarget)
- {
- flightCamera.transform.rotation = Quaternion.LookRotation(lastTargetPosition - flightCamera.transform.position, cameraUp);
}
+ cameraParent.transform.position = vessel.CoM; // Note don't set cameraParent.transform.rotation as it messes with the Lerping.
-
-
- if(vessel != null)
+ if (dogfightVelocityChase && !dogfightChasePlaneMode)
{
- cameraParent.transform.position = manualPosition + (vessel.CoM - vessel.rb_velocity * Time.fixedDeltaTime);
-
- if(referenceMode == ReferenceModes.Surface)
+ var lastDogfightLastTargetPosition = dogfightLastTargetPosition;
+ if (vessel.Speed() > 1 && !vessel.InOrbit())
{
- flightCamera.transform.position -= Time.fixedDeltaTime * Mathf.Clamp((float)vessel.srf_velocity.magnitude, 0, maxRelV) * vessel.srf_velocity.normalized;
+ dogfightLastTargetPosition = vessel.CoM + vessel.Velocity().normalized * 5000f;
}
- else if(referenceMode == ReferenceModes.Orbit)
+ else
{
- flightCamera.transform.position -= Time.fixedDeltaTime * Mathf.Clamp((float)vessel.obt_velocity.magnitude, 0, maxRelV) * vessel.obt_velocity.normalized;
+ dogfightLastTargetPosition = vessel.CoM + (vessel.isEVA ? vesselTransform.forward : vesselTransform.up) * 5000f;
}
- else if(referenceMode == ReferenceModes.InitialVelocity)
+ if (vessel.Splashed && vessel.altitude > -vesselRadius && vessel.Speed() < 10) // Don't bob around lots if the vessel is in water.
{
- Vector3 camVelocity = Vector3.zero;
- if(useOrbital && initialOrbit != null)
- {
- camVelocity = (initialOrbit.getOrbitalVelocityAtUT(Planetarium.GetUniversalTime()).xzy - vessel.GetObtVelocity());
- }
- else
- {
- camVelocity = (initialVelocity - vessel.srf_velocity);
- }
- flightCamera.transform.position += camVelocity * Time.fixedDeltaTime;
+ dogfightLastTargetPosition = Vector3.Lerp(lastDogfightLastTargetPosition, Vector3.ProjectOnPlane(dogfightLastTargetPosition, cameraUp), (float)vessel.Speed() * 0.01f); // Slow lerp to a horizontal position.
}
}
+ //roll
+ if (dogfightRoll > 0 && !vessel.LandedOrSplashed && !vessel.isEVA && !bdArmory.isBDMissile && !dogfightChasePlaneMode)
+ {
+ var vesselRollTarget = Quaternion.RotateTowards(Quaternion.identity, Quaternion.FromToRotation(cameraUp, -vesselTransform.forward), dogfightRoll * Vector3.Angle(cameraUp, -vesselTransform.forward));
+ dogfightCameraRoll = Quaternion.Lerp(dogfightCameraRoll, vesselRollTarget, dogfightLerp);
+ dogfightCameraRollUp = dogfightCameraRoll * cameraUp;
+ }
+ else
+ {
+ dogfightCameraRollUp = cameraUp;
+ }
- //mouse panning, moving
- Vector3 forwardLevelAxis = (Quaternion.AngleAxis(-90, cameraUp) * flightCamera.transform.right).normalized;
- Vector3 rightAxis = (Quaternion.AngleAxis(90, forwardLevelAxis) * cameraUp).normalized;
-
- //free move
- if(enableKeypad)
+ if (!(freeLook && fmPivotMode == FMPivotModes.Target)) // Free-look pivoting around the target overrides positioning.
{
- if(Input.GetKey(fmUpKey))
+ Vector3 lagDirection = dogfightChasePlaneMode ?
+ (vessel.Speed() > 1 ? vessel.Velocity().normalized : (chasePlaneTargetIsEVA ? vesselTransform.forward : vesselTransform.up)) :
+ (dogfightLastTargetPosition - vessel.CoM).normalized;
+ Vector3 offsetDirectionY = dogfightOffsetMode switch
{
- manualPosition += cameraUp * freeMoveSpeed * Time.fixedDeltaTime;
- }
- else if(Input.GetKey(fmDownKey))
- {
- manualPosition -= cameraUp * freeMoveSpeed * Time.fixedDeltaTime;
- }
- if(Input.GetKey(fmForwardKey))
+ DogfightOffsetMode.Camera => dogfightCameraRollUp,
+ DogfightOffsetMode.Vessel => -vesselTransform.forward,
+ _ => cameraUp
+ };
+ Vector3 offsetDirectionX = Vector3.Cross(offsetDirectionY, lagDirection).normalized;
+ Vector3 offset = -dogfightDistance * lagDirection;
+ if (!vessel.isEVA) offset += (dogfightOffsetX * offsetDirectionX) + (dogfightOffsetY * offsetDirectionY);
+ Vector3 camPos = vessel.CoM + offset;
+
+ Vector3 localCamPos = cameraParent.transform.InverseTransformPoint(camPos);
+ if (dogfightInertialChaseMode && dogfightInertialFactor > 0)
{
- manualPosition += forwardLevelAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ dogfightLerpMomentum /= dogfightLerpMomentum.magnitude / dogfightDistance + 1.1f - 0.1f * dogfightInertialFactor;
+ dogfightLerpMomentum += dogfightLerpDelta * 0.1f * dogfightInertialFactor;
+ dogfightLerpDelta = -cameraTransform.localPosition;
}
- else if(Input.GetKey(fmBackKey))
+ cameraTransform.localPosition = Vector3.Lerp(cameraTransform.localPosition, localCamPos, dogfightLerp);
+ if (dogfightInertialChaseMode && dogfightInertialFactor > 0)
{
- manualPosition -= forwardLevelAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ cameraTransform.localPosition += dogfightLerpMomentum;
+ dogfightLerpDelta += cameraTransform.localPosition;
+ if (dogfightLerpDelta.sqrMagnitude > dogfightDistance * dogfightDistance) dogfightLerpDelta *= dogfightDistance / dogfightLerpDelta.magnitude;
}
- if(Input.GetKey(fmLeftKey))
+ if (DEBUG2 && !GameIsPaused)
{
- manualPosition -= flightCamera.transform.right * freeMoveSpeed * Time.fixedDeltaTime;
+ // Debug2Log("time scale: " + Time.timeScale.ToString("G3") + ", Δt: " + Time.fixedDeltaTime.ToString("G3"));
+ // Debug2Log("offsetDirection: " + offsetDirectionX.ToString("G3"));
+ // Debug2Log("target offset: " + ((vessel.CoM - dogfightLastTargetPosition).normalized * dogfightDistance).ToString("G4"));
+ // Debug2Log("xOff: " + (dogfightOffsetX * offsetDirectionX).ToString("G3"));
+ // Debug2Log("yOff: " + (dogfightOffsetY * dogfightCameraRollUp).ToString("G3"));
+ // Debug2Log("camPos - vessel.CoM: " + (camPos - vessel.CoM).ToString("G3"));
+ // Debug2Log("localCamPos: " + localCamPos.ToString("G3") + ", " + cameraTransform.localPosition.ToString("G3"));
+ // Debug2Log($"lerp momentum: {dogfightLerpMomentum:G3}");
+ // Debug2Log($"lerp delta: {dogfightLerpDelta:G3}");
}
- else if(Input.GetKey(fmRightKey))
+ // Avoid views from below water / terrain when appropriate.
+ if (vessel.altitude > -vesselRadius && (vessel.LandedOrSplashed || vessel.radarAltitude < dogfightDistance))
{
- manualPosition += flightCamera.transform.right * freeMoveSpeed * Time.fixedDeltaTime;
+ var cameraRadarAltitude = GetRadarAltitudeAtPos(cameraTransform.position);
+ if (cameraRadarAltitude < 5) cameraTransform.position += (5f - cameraRadarAltitude) * cameraUp; // Prevent viewing from under the surface if near the surface.
}
+ }
- //keyZoom
- if(!autoFOV)
+ //rotation
+ if (Input.GetKey(KeyCode.Mouse1)) // Free-look
+ {
+ if (!freeLook)
{
- if(Input.GetKey(fmZoomInKey))
- {
- zoomExp = Mathf.Clamp(zoomExp + (keyZoomSpeed * Time.fixedDeltaTime), 1, 8);
- }
- else if(Input.GetKey(fmZoomOutKey))
+ freeLookStartUpDistance.x += Input.GetAxis("Mouse X");
+ freeLookStartUpDistance.y += -Input.GetAxis("Mouse Y");
+ if (freeLookStartUpDistance.sqrMagnitude > freeLookThresholdSqr)
{
- zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, 8);
+ freeLook = true;
+ var vesselCameraOffset = vessel.CoM - cameraTransform.position;
+ freeLookOffset = Vector3.Dot(vesselCameraOffset, cameraTransform.forward) * cameraTransform.forward - vesselCameraOffset;
+ freeLookDistance = (vesselCameraOffset + freeLookOffset).magnitude;
}
}
- else
+ }
+ else
+ {
+ freeLookStartUpDistance = Vector2.zero;
+ if (freeLook)
{
- if(Input.GetKey(fmZoomInKey))
- {
- autoZoomMargin = Mathf.Clamp(autoZoomMargin + (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, 50);
- }
- else if(Input.GetKey(fmZoomOutKey))
- {
- autoZoomMargin = Mathf.Clamp(autoZoomMargin - (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, 50);
- }
+ freeLook = false;
+ if (MouseAimFlight.IsMouseAimActive) MouseAimFlight.SetFreeLookCooldown(1); // Give it 1s for the camera orientation to recover before resuming applying our modification to the MouseAimFlight target.
}
}
-
-
- if(camTarget == null && Input.GetKey(KeyCode.Mouse1))
+ Vector2 controllerInput = GetControllerInput(scale: 3f, inverted: true); // Controller input: .x => hdg, .y => pitch
+ if (controllerInput != default)
+ {
+ if (!freeLook)
+ {
+ var vesselCameraOffset = vessel.CoM - cameraTransform.position;
+ freeLookOffset = Vector3.Dot(vesselCameraOffset, cameraTransform.forward) * cameraTransform.forward - vesselCameraOffset;
+ freeLookDistance = (vesselCameraOffset + freeLookOffset).magnitude;
+ }
+ freeLook = true;
+ }
+ if (freeLook)
{
- flightCamera.transform.rotation *= Quaternion.AngleAxis(Input.GetAxis("Mouse X") * 1.7f, Vector3.up); //*(Mathf.Abs(Mouse.delta.x)/7)
- flightCamera.transform.rotation *= Quaternion.AngleAxis(-Input.GetAxis("Mouse Y") * 1.7f, Vector3.right);
- flightCamera.transform.rotation = Quaternion.LookRotation(flightCamera.transform.forward, cameraUp);
+ var rotationAdjustment = controllerInput != default ?
+ Quaternion.AngleAxis(controllerInput.x, Vector3.up) * Quaternion.AngleAxis(controllerInput.y, Vector3.right) :
+ Quaternion.AngleAxis(Input.GetAxis("Mouse X") * 3f, Vector3.up) * Quaternion.AngleAxis(-Input.GetAxis("Mouse Y") * 3f, Vector3.right);
+ cameraTransform.rotation *= rotationAdjustment;
+ cameraTransform.rotation = Quaternion.LookRotation(cameraTransform.forward, dogfightCameraRollUp);
+ if (fmPivotMode == FMPivotModes.Target) { cameraTransform.position = vessel.CoM + freeLookOffset - freeLookDistance * cameraTransform.forward; }
}
- if(Input.GetKey(KeyCode.Mouse2))
+ else
{
- manualPosition += flightCamera.transform.right * Input.GetAxis("Mouse X") * 2;
- manualPosition += forwardLevelAxis * Input.GetAxis("Mouse Y") * 2;
+ var rotationTarget = Vector3.Lerp(vessel.CoM, dogfightLastTargetPosition, 0.5f);
+ if (dogfightInertialChaseMode && dogfightInertialFactor > 0 && !dogfightChasePlaneMode)
+ {
+ dogfightRotationTarget = Vector3.Lerp(dogfightRotationTarget, rotationTarget, dogfightLerp * (1f - 0.5f * dogfightInertialFactor));
+ rotationTarget = dogfightRotationTarget;
+ }
+ if (dogfightChasePlaneMode || dogfightInertialChaseMode)
+ cameraTransform.rotation = Quaternion.Slerp(cameraTransform.rotation, Quaternion.LookRotation(rotationTarget - cameraTransform.position, dogfightCameraRollUp), dogfightLerp * (0.2f + 0.8f * dogfightRoll));
+ else
+ cameraTransform.rotation = Quaternion.LookRotation(rotationTarget - cameraTransform.position, dogfightCameraRollUp);
+ if (MouseAimFlight.IsMouseAimActive)
+ {
+ if (!MouseAimFlight.IsInFreeLookRecovery)
+ {
+ // mouseAimFlightTarget keeps the target stationary (i.e., no change from the default)
+ // cameraTransform.TransformDirection(mouseAimFlightTargetLocal) moves the target fully with the camera
+ var newMouseAimFlightTarget = cameraTransform.TransformDirection(mouseAimFlightTargetLocal);
+ newMouseAimFlightTarget = Vector3.Lerp(newMouseAimFlightTarget, mouseAimFlightTarget, Mathf.Min((newMouseAimFlightTarget - mouseAimFlightTarget).magnitude * 0.01f, 0.5f));
+ MouseAimFlight.SetMouseAimTarget(newMouseAimFlightTarget); // Adjust how MouseAimFlight updates the target position for easier control in combat.
+ }
+ }
}
- manualPosition += cameraUp * 10 * Input.GetAxis("Mouse ScrollWheel");
//autoFov
- if(camTarget != null && autoFOV)
+ if (autoFOV)
{
- float cameraDistance = Vector3.Distance(camTarget.transform.position, flightCamera.transform.position);
- float targetFoV = Mathf.Clamp((7000 / (cameraDistance + 100)) - 14 + autoZoomMargin, 2, 60);
- //flightCamera.SetFoV(targetFoV);
+ float targetFoV;
+ if (dogfightVelocityChase)
+ {
+ targetFoV = Mathf.Clamp((7000 / (dogfightDistance + 100)) - 14 + autoZoomMargin, 2, 60);
+ }
+ else
+ {
+ float angle = Vector3.Angle(dogfightLastTargetPosition - cameraTransform.position, vessel.CoM - cameraTransform.position);
+ targetFoV = Mathf.Clamp(angle + autoZoomMargin, 0.1f, 60f);
+ }
manualFOV = targetFoV;
}
//FOV
- if(!autoFOV)
+ if (!autoFOV)
{
zoomFactor = Mathf.Exp(zoomExp) / Mathf.Exp(1);
manualFOV = 60 / zoomFactor;
updateFOV = (currentFOV != manualFOV);
- if(updateFOV)
+ if (updateFOV)
{
currentFOV = Mathf.Lerp(currentFOV, manualFOV, 0.1f);
flightCamera.SetFoV(currentFOV);
@@ -814,62 +1390,1078 @@ void UpdateStationaryCamera()
else
{
currentFOV = Mathf.Lerp(currentFOV, manualFOV, 0.1f);
- flightCamera.SetFoV(currentFOV);
+ flightCamera.SetFoV(currentFOV);
zoomFactor = 60 / currentFOV;
}
- lastPosition = flightCamera.transform.position;
- lastRotation = flightCamera.transform.rotation;
+ //free move
+ if (enableKeypad && !boundThisFrame)
+ {
+ switch (fmMode)
+ {
+ case FMModeTypes.Position:
+ {
+ if (Input.GetKey(fmUpKey))
+ {
+ dogfightOffsetY += freeMoveSpeed * Time.fixedDeltaTime;
+ dogfightOffsetY = Mathf.Clamp(dogfightOffsetY, -dogfightMaxOffset, dogfightMaxOffset);
+ if (textInput) inputFields["dogfightOffsetY"].currentValue = dogfightOffsetY;
+ }
+ else if (Input.GetKey(fmDownKey))
+ {
+ dogfightOffsetY -= freeMoveSpeed * Time.fixedDeltaTime;
+ dogfightOffsetY = Mathf.Clamp(dogfightOffsetY, -dogfightMaxOffset, dogfightMaxOffset);
+ if (textInput) inputFields["dogfightOffsetY"].currentValue = dogfightOffsetY;
+ }
+ if (Input.GetKey(fmForwardKey))
+ {
+ dogfightDistance -= freeMoveSpeed * Time.fixedDeltaTime;
+ dogfightDistance = Mathf.Clamp(dogfightDistance, 1f, dogfightMaxDistance);
+ if (textInput) inputFields["dogfightDistance"].currentValue = dogfightDistance;
+ }
+ else if (Input.GetKey(fmBackKey))
+ {
+ dogfightDistance += freeMoveSpeed * Time.fixedDeltaTime;
+ dogfightDistance = Mathf.Clamp(dogfightDistance, 1f, dogfightMaxDistance);
+ if (textInput) inputFields["dogfightDistance"].currentValue = dogfightDistance;
+ }
+ if (Input.GetKey(fmLeftKey))
+ {
+ dogfightOffsetX -= freeMoveSpeed * Time.fixedDeltaTime;
+ dogfightOffsetX = Mathf.Clamp(dogfightOffsetX, -dogfightMaxOffset, dogfightMaxOffset);
+ if (textInput) inputFields["dogfightOffsetX"].currentValue = dogfightOffsetX;
+ }
+ else if (Input.GetKey(fmRightKey))
+ {
+ dogfightOffsetX += freeMoveSpeed * Time.fixedDeltaTime;
+ dogfightOffsetX = Mathf.Clamp(dogfightOffsetX, -dogfightMaxOffset, dogfightMaxOffset);
+ if (textInput) inputFields["dogfightOffsetX"].currentValue = dogfightOffsetX;
+ }
+ //keyZoom
+ if (!autoFOV)
+ {
+ if (Input.GetKey(fmZoomInKey))
+ {
+ zoomExp = Mathf.Clamp(zoomExp + (keyZoomSpeed * Time.fixedDeltaTime), 1, zoomMaxExp);
+ if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1);
+ }
+ else if (Input.GetKey(fmZoomOutKey))
+ {
+ zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, zoomMaxExp);
+ if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1);
+ }
+ }
+ else
+ {
+ if (Input.GetKey(fmZoomInKey))
+ {
+ autoZoomMargin = Mathf.Clamp(autoZoomMargin + (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, autoZoomMarginMax);
+ if (textInput) inputFields["autoZoomMargin"].currentValue = autoZoomMargin;
+ }
+ else if (Input.GetKey(fmZoomOutKey))
+ {
+ autoZoomMargin = Mathf.Clamp(autoZoomMargin - (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, autoZoomMarginMax);
+ if (textInput) inputFields["autoZoomMargin"].currentValue = autoZoomMargin;
+ }
+ }
+ }
+ break;
+ case FMModeTypes.Speed:
+ {
+ if (Input.GetKey(fmUpKey))
+ {
+ fmSpeeds.y += freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmDownKey))
+ {
+ fmSpeeds.y -= freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmForwardKey))
+ {
+ fmSpeeds.z -= freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmBackKey))
+ {
+ fmSpeeds.z += freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmLeftKey))
+ {
+ fmSpeeds.x -= freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmRightKey))
+ {
+ fmSpeeds.x += freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmZoomInKey))
+ {
+ fmSpeeds.w += keyZoomSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmZoomOutKey))
+ {
+ fmSpeeds.w -= keyZoomSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ }
+ break;
+ }
+ }
//vessel camera shake
- if(shakeMultiplier > 0)
+ if (shakeMultiplier > 0)
{
- foreach(var v in FlightGlobals.Vessels)
+ foreach (var v in FlightGlobals.Vessels)
{
- if(!v || !v.loaded || v.packed) continue;
+ if (!v || !v.loaded || v.packed || v.isActiveVessel) continue;
VesselCameraShake(v);
}
}
- UpdateCameraShake();
- }
-
- void LateUpdate()
- {
-
- //retain pos and rot after vessel destruction
- if (cameraToolActive && flightCamera.transform.parent != cameraParent.transform)
+ if (BDArmory.hasBDA && (bdArmory.hasBDAI || bdArmory.isBDMissile) && (bdArmory.useBDAutoTarget || (bdArmory.useCentroid && bdArmory.bdWMVessels.Count < 2)) && !dogfightChasePlaneMode)
{
- flightCamera.SetTargetNone();
- flightCamera.transform.parent = null;
- flightCamera.transform.position = lastPosition;
- flightCamera.transform.rotation = lastRotation;
- hasDied = true;
- diedTime = Time.time;
+ bdArmory.UpdateAIDogfightTarget();
+ if (bdArmory.isRunningWaypoints)
+ {
+ dogfightLastTarget = false;
+ dogfightVelocityChase = true;
+ }
}
-
}
+ #endregion
- void UpdateCameraShake()
+ #region Stationary Camera
+ Quaternion stationaryCameraRoll = Quaternion.identity;
+ void StartStationaryCamera()
{
- if(shakeMultiplier > 0)
+ toolMode = ToolModes.StationaryCamera;
+ var cameraTransform = flightCamera.transform;
+ if (FlightGlobals.ActiveVessel != null)
{
- if(shakeMagnitude > 0.1f)
+ if (DEBUG)
{
- Vector3 shakeAxis = UnityEngine.Random.onUnitSphere;
- shakeOffset = Mathf.Sin(shakeMagnitude * 20 * Time.time) * (shakeMagnitude / 10) * shakeAxis;
+ message = "Starting stationary camera.";
+ Debug.Log("[CameraTools]: " + message);
+ DebugLog(message);
+ }
+ hasDied = false;
+ vessel = FlightGlobals.ActiveVessel;
+ var vesselTransform = vessel.ReferenceTransform != null ? vessel.ReferenceTransform : vessel.vesselTransform; // Use the reference transform, but fall back to the regular vesselTransform if it's null.
+ cameraUp = vessel.up;
+ if (origMode == FlightCamera.Modes.ORBITAL || (origMode == FlightCamera.Modes.AUTO && FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.ORBITAL))
+ {
+ cameraUp = Vector3.up;
}
+ rightAxis = -Vector3.Cross(vessel.Velocity(), vessel.up).normalized;
+ SetCameraParent(deathCam.transform, true); // First update the cameraParent to the last deathCam configuration offset for the active vessel's CoM.
- flightCamera.transform.rotation = Quaternion.AngleAxis((shakeMultiplier/2) * shakeMagnitude / 50f, Vector3.ProjectOnPlane(UnityEngine.Random.onUnitSphere, flightCamera.transform.forward)) * flightCamera.transform.rotation;
- }
+ manualPosition = Vector3.zero;
+ if (randomMode)
+ {
+ camTarget = FlightGlobals.ActiveVessel.GetReferenceTransformPart();
+ if (camTarget == null) // Sometimes the vessel doesn't have the reference transform part set up. It ought to be the root part usually.
+ camTarget = FlightGlobals.ActiveVessel.rootPart;
+ }
+ hasTarget = camTarget != null;
+ lastVesselCoM = vessel.CoM;
- shakeMagnitude = Mathf.Lerp(shakeMagnitude, 0, 5*Time.fixedDeltaTime);
- }
+ // Camera position.
+ if (!randomMode && autoLandingPosition && GetAutoLandingPosition()) // Set up a landing shot if possible or fall back on other methods.
+ { }
+ else if (autoFlybyPosition || randomMode)
+ {
+ setPresetOffset = false;
+
+ float clampedSpeed = Mathf.Clamp((float)vessel.srfSpeed, 0, Mathf.Abs(maxRelV));
+ float sideDistance = Mathf.Min(20 + vesselRadius + (clampedSpeed / 10), 150);
+ float distanceAhead = Mathf.Clamp(4 * clampedSpeed + vesselRadius, 30 + vesselRadius, 3500) * Mathf.Sign(maxRelV);
+
+ if (vessel.Velocity().sqrMagnitude > 1)
+ { cameraTransform.position = vessel.CoM + distanceAhead * vessel.Velocity().normalized; }
+ else
+ { cameraTransform.position = vessel.CoM + distanceAhead * vesselTransform.up; }
+
+ if (flightCamera.mode == FlightCamera.Modes.FREE || FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.FREE)
+ {
+ cameraTransform.position += (sideDistance * rightAxis) + (15 * cameraUp);
+ }
+ else if (flightCamera.mode == FlightCamera.Modes.ORBITAL || FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.ORBITAL)
+ {
+ cameraTransform.position += (sideDistance * FlightGlobals.getUpAxis()) + (15 * Vector3.up);
+ }
+
+ // Correct for being below terrain/water (min of 30m AGL).
+ if (vessel.altitude > -vesselRadius) // Not too far below water.
+ {
+ var cameraRadarAltitude = GetRadarAltitudeAtPos(cameraTransform.position);
+ if (cameraRadarAltitude < 30)
+ {
+ cameraTransform.position += (30 - cameraRadarAltitude) * cameraUp;
+ }
+ }
+ if (vessel.altitude > 0 && vessel.radarAltitude > 0) // Make sure terrain isn't in the way (as long as the target is above ground).
+ {
+ int count = 0;
+ Ray ray;
+ RaycastHit hit;
+ do
+ {
+ // Note: we have to use vessel.transform.position instead of vessel.CoM here as the CoM can be below terrain for weird parts.
+ ray = new Ray(cameraTransform.position, vessel.transform.position - cameraTransform.position);
+ if (Physics.Raycast(ray, out hit, (cameraTransform.position - vessel.transform.position).magnitude, 1 << 15)) // Just terrain.
+ {
+ cameraTransform.position += 50 * cameraUp; // Try 50m higher.
+ }
+ else
+ {
+ break;
+ } // We're clear.
+ } while (hit.collider != null && ++count < 100); // Max 5km higher.
+ }
+ }
+ else if (manualOffset)
+ {
+ setPresetOffset = false;
+ float sideDistance = manualOffsetRight;
+ float distanceAhead = manualOffsetForward;
+
+ if (vessel.Velocity().sqrMagnitude > 1)
+ { cameraTransform.position = vessel.CoM + distanceAhead * vessel.Velocity().normalized; }
+ else
+ { cameraTransform.position = vessel.CoM + distanceAhead * vesselTransform.up; }
+
+ if (flightCamera.mode == FlightCamera.Modes.FREE || FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.FREE)
+ {
+ cameraTransform.position += (sideDistance * rightAxis) + (manualOffsetUp * cameraUp);
+ }
+ else if (flightCamera.mode == FlightCamera.Modes.ORBITAL || FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.ORBITAL)
+ {
+ cameraTransform.position += (sideDistance * FlightGlobals.getUpAxis()) + (manualOffsetUp * Vector3.up);
+ }
+ }
+ else if (setPresetOffset)
+ {
+ cameraTransform.position = presetOffset;
+ //setPresetOffset = false;
+ }
+
+ // Camera rotation.
+ if (hasTarget)
+ {
+ cameraTransform.rotation = Quaternion.LookRotation(vessel.CoM - cameraTransform.position, cameraUp);
+ }
+
+ // Initial velocity
+ initialVelocity = vessel.Velocity();
+ initialOrbit = new Orbit(vessel.orbit);
+
+ cameraToolActive = true;
+
+ ResetDoppler();
+ if (OnResetCTools != null)
+ { OnResetCTools(); }
+ SetDoppler(true);
+ AddAtmoAudioControllers(true);
+ }
+ else
+ {
+ Debug.Log("[CameraTools]: Stationary Camera failed. Active Vessel is null.");
+ }
+ if (hasSavedRotation) { cameraTransform.rotation = savedRotation; }
+ stationaryCameraRoll = Quaternion.FromToRotation(Vector3.ProjectOnPlane(cameraUp, cameraTransform.forward), cameraTransform.up);
+ }
+
+ ///
+ /// Get the auto-landing position.
+ /// This is the vessel's current position + the manual offset.
+ /// If maintain velocity is enabled, then add an additional horizontal component for where the craft would land if it follows a ballistic trajectory, assuming flat terrain.
+ ///
+ ///
+ bool GetAutoLandingPosition()
+ {
+ if (maintainInitialVelocity && !(vessel.situation == Vessel.Situations.FLYING || vessel.situation == Vessel.Situations.SUB_ORBITAL)) return false; // In orbit or on the surface already.
+ var velForwardAxis = Vector3.ProjectOnPlane(vessel.srf_vel_direction, cameraUp).normalized;
+ var velRightAxis = Vector3.Cross(cameraUp, velForwardAxis);
+ var position = vessel.CoM + velForwardAxis * manualOffsetForward + velRightAxis * manualOffsetRight;
+ var heightAboveTerrain = GetRadarAltitudeAtPos(position);
+ if (maintainInitialVelocity) // Predict where the landing is going to be assuming it follows a ballistic trajectory.
+ {
+ var gravity = -FlightGlobals.getGeeForceAtPosition(vessel.CoM).magnitude;
+ int count = 0;
+ float velOffset = 0;
+ float lastVelOffset = velOffset;
+ do
+ {
+ var timeToLanding = (-vessel.verticalSpeed - MathUtils.Sqrt(vessel.verticalSpeed * vessel.verticalSpeed - 2 * gravity * heightAboveTerrain)) / gravity; // G is <0, so - branch is always the right one.
+ lastVelOffset = velOffset;
+ velOffset = (float)(vessel.horizontalSrfSpeed * timeToLanding);
+ position = vessel.CoM + velForwardAxis * (manualOffsetForward + velOffset) + velRightAxis * manualOffsetRight;
+ heightAboveTerrain = GetRadarAltitudeAtPos(position);
+ } while (++count < 10 && Mathf.Abs(velOffset - lastVelOffset) > 1f); // Up to 10 iterations to find a somewhat stable solution (within 1m).
+ }
+ flightCamera.transform.position = position + (manualOffsetUp - heightAboveTerrain) * cameraUp; // Correct the camera altitude.
+ autoLandingCamEnabled = true;
+ return true;
+ }
+
+ Vector3 lastOffset = Vector3.zero;
+ Vector3 offsetSinceLastFrame = Vector3.zero;
+ Vector3 lastOffsetSinceLastFrame = Vector3.zero;
+ Vector3 lastCameraPosition = Vector3.zero;
+ Vector3 lastCamParentPosition = Vector3.zero;
+ void UpdateStationaryCamera()
+ {
+ if (useAudioEffects)
+ {
+ speedOfSound = 233 * MathUtils.Sqrt(1 + (FlightGlobals.getExternalTemperature(vessel.GetWorldPos3D(), vessel.mainBody) / 273.15));
+ // if (DEBUG) Debug.Log($"[CameraTools]: Speed of sound is {speedOfSound:G5}m/s");
+ }
+
+ if (flightCamera.Target != null) flightCamera.SetTargetNone(); // Don't go to the next vessel if the vessel is destroyed.
+
+ var cameraTransform = flightCamera.transform;
+ bool fmMovementModified = Input.GetKey(fmMovementModifier);
+ if (fmMovementModified)
+ {
+ upAxis = cameraTransform.up;
+ forwardAxis = cameraTransform.forward;
+ rightAxis = cameraTransform.right;
+ }
+ else
+ {
+ if (Input.GetKeyUp(fmMovementModifier)) // Modifier key released
+ {
+ stationaryCameraRoll = Quaternion.FromToRotation(Vector3.ProjectOnPlane(cameraUp, cameraTransform.forward), cameraTransform.up); // Correct for any adjustments to the roll from camera movements.
+ }
+ upAxis = stationaryCameraRoll * cameraUp;
+ forwardAxis = Vector3.RotateTowards(upAxis, cameraTransform.forward, Mathf.Deg2Rad * 90, 0).normalized;
+ rightAxis = Vector3.Cross(upAxis, forwardAxis);
+ }
+
+ // Set camera position before rotation to avoid jitter.
+ if (vessel != null)
+ {
+ lastCameraPosition = cameraTransform.position;
+ offsetSinceLastFrame = vessel.CoM - lastVesselCoM;
+ if (DEBUG2 && !GameIsPaused && !offsetSinceLastFrame.IsZero())
+ {
+ lastOffsetSinceLastFrame = offsetSinceLastFrame;
+ }
+ lastVesselCoM = vessel.CoM;
+ cameraParent.transform.position = manualPosition + vessel.CoM;
+ if (vessel.srfSpeed > maxRelV / 2 && offsetSinceLastFrame.sqrMagnitude > signedMaxRelVSqr * TimeWarp.fixedDeltaTime * TimeWarp.fixedDeltaTime) // Account for maxRelV. Note: we use fixedDeltaTime here as we're interested in how far it jumped on the physics update. Also check for srfSpeed to account for changes in CoM when on launchpad (srfSpeed < maxRelV/2 should be good for maxRelV down to around 1 in most cases).
+ {
+ offsetSinceLastFrame = maxRelV * TimeWarp.fixedDeltaTime * offsetSinceLastFrame.normalized;
+ }
+ if (!offsetSinceLastFrame.IsZero()) cameraTransform.position -= offsetSinceLastFrame;
+ }
+
+ // if (DEBUG2 && !GameIsPaused)
+ // {
+ // var Δ = lastOffset - (vessel.CoM - cameraTransform.position);
+ // Debug2Log("situation: " + vessel.situation);
+ // Debug2Log("warp mode: " + TimeWarp.WarpMode + ", fixedDeltaTime: " + TimeWarp.fixedDeltaTime);
+ // Debug2Log("floating origin offset: " + FloatingOrigin.Offset.ToString("G6"));
+ // Debug2Log("offsetNonKB: " + FloatingOrigin.OffsetNonKrakensbane.ToString("G6"));
+ // Debug2Log("vv*Δt: " + (vessel.obt_velocity * TimeWarp.fixedDeltaTime).ToString("G6"));
+ // Debug2Log("sv*Δt: " + (vessel.srf_velocity * TimeWarp.fixedDeltaTime).ToString("G6"));
+ // Debug2Log("kv*Δt: " + (Krakensbane.GetFrameVelocity() * TimeWarp.fixedDeltaTime).ToString("G6"));
+ // Debug2Log("ΔKv: " + Krakensbane.GetLastCorrection().ToString("G6"));
+ // Debug2Log("sv*Δt-onkb: " + (vessel.srf_velocity * TimeWarp.fixedDeltaTime - FloatingOrigin.OffsetNonKrakensbane).ToString("G6"));
+ // Debug2Log("kv*Δt-onkb: " + (Krakensbane.GetFrameVelocity() * TimeWarp.fixedDeltaTime - FloatingOrigin.OffsetNonKrakensbane).ToString("G6"));
+ // Debug2Log("floatingKrakenAdjustment: " + floatingKrakenAdjustment.ToString("G6"));
+ // Debug2Log("(sv-kv)*Δt" + ((vessel.srf_velocity - Krakensbane.GetFrameVelocity()) * TimeWarp.fixedDeltaTime).ToString("G6"));
+ // Debug2Log("Parent pos: " + cameraParent.transform.position.ToString("G6"));
+ // Debug2Log("Camera pos: " + cameraTransform.position.ToString("G6"));
+ // Debug2Log("ΔCamera: " + (cameraTransform.position - lastCameraPosition).ToString("G6"));
+ // Debug2Log("δp: " + (cameraParent.transform.position - lastCamParentPosition).ToString("G6"));
+ // Debug2Log("ΔCamera + δp: " + (cameraTransform.position - lastCameraPosition + cameraParent.transform.position - lastCamParentPosition).ToString("G6"));
+ // Debug2Log("δ: " + lastOffsetSinceLastFrame.ToString("G6"));
+ // Debug2Log("Δ: " + Δ.ToString("G6"));
+ // Debug2Log("δ + Δ: " + (lastOffsetSinceLastFrame + Δ).ToString("G6"));
+ // lastOffset = vessel.CoM - cameraTransform.position;
+ // lastCamParentPosition = cameraParent.transform.position;
+ // }
+
+ // Keypad input
+ if (enableKeypad && !boundThisFrame)
+ {
+ switch (fmMode)
+ {
+ case FMModeTypes.Position:
+ {
+ if (Input.GetKey(fmUpKey))
+ {
+ manualPosition += upAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmDownKey))
+ {
+ manualPosition -= upAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmForwardKey))
+ {
+ manualPosition += forwardAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmBackKey))
+ {
+ manualPosition -= forwardAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmLeftKey))
+ {
+ manualPosition -= rightAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmRightKey))
+ {
+ manualPosition += rightAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ }
+
+ //keyZoom
+ if (!autoFOV)
+ {
+ if (Input.GetKey(fmZoomInKey))
+ {
+ zoomExp = Mathf.Clamp(zoomExp + (keyZoomSpeed * Time.fixedDeltaTime), 1, zoomMaxExp);
+ if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1);
+ }
+ else if (Input.GetKey(fmZoomOutKey))
+ {
+ zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, zoomMaxExp);
+ if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1);
+ }
+ }
+ else
+ {
+ if (Input.GetKey(fmZoomInKey))
+ {
+ autoZoomMargin = Mathf.Clamp(autoZoomMargin + (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, autoZoomMarginMax);
+ if (textInput) inputFields["autoZoomMargin"].currentValue = autoZoomMargin;
+ }
+ else if (Input.GetKey(fmZoomOutKey))
+ {
+ autoZoomMargin = Mathf.Clamp(autoZoomMargin - (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, autoZoomMarginMax);
+ if (textInput) inputFields["autoZoomMargin"].currentValue = autoZoomMargin;
+ }
+ }
+ }
+ break;
+ case FMModeTypes.Speed:
+ {
+ if (Input.GetKey(fmUpKey))
+ {
+ fmSpeeds.y += freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmDownKey))
+ {
+ fmSpeeds.y -= freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmForwardKey))
+ {
+ fmSpeeds.z += freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmBackKey))
+ {
+ fmSpeeds.z -= freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmLeftKey))
+ {
+ fmSpeeds.x -= freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmRightKey))
+ {
+ fmSpeeds.x += freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmZoomInKey))
+ {
+ fmSpeeds.w += keyZoomSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmZoomOutKey))
+ {
+ fmSpeeds.w -= keyZoomSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ }
+ break;
+ }
+
+ if (GetKeyPress(resetRollKey)) ResetRoll();
+ }
+
+ // Mouse input
+ if (Input.GetKeyDown(KeyCode.Mouse1) || Input.GetKeyDown(KeyCode.Mouse2))
+ {
+ stationaryCameraRoll = Quaternion.FromToRotation(Vector3.ProjectOnPlane(cameraUp, cameraTransform.forward), cameraTransform.up); // Correct for any adjustments to the roll from previous camera movements.
+ }
+ if (Input.GetKey(KeyCode.Mouse1) && Input.GetKey(KeyCode.Mouse2))
+ {
+ stationaryCameraRoll = Quaternion.AngleAxis(Input.GetAxis("Mouse X") * -2f, cameraTransform.forward) * stationaryCameraRoll;
+ upAxis = stationaryCameraRoll * cameraUp;
+ cameraTransform.rotation = Quaternion.LookRotation(cameraTransform.forward, upAxis);
+ }
+ else
+ {
+ Vector2 angle = Input.GetKey(KeyCode.Mouse1) ? new(Input.GetAxis("Mouse X") * 2f, -Input.GetAxis("Mouse Y") * 2f) : default;
+ angle += GetControllerInput(inverted: fmPivotMode == FMPivotModes.Camera); // Controller input: .x => hdg, .y => pitch
+ if (angle != default)
+ {
+ if (camTarget == null && !hasTarget) // Local rotation can be overridden
+ {
+ // Pivoting around a target makes no sense here as there's no target to pivot around, so we just pivot around the camera.
+ cameraTransform.rotation *= Quaternion.AngleAxis(angle.x, Vector3.up) * Quaternion.AngleAxis(angle.y, Vector3.right);
+ cameraTransform.rotation = Quaternion.LookRotation(cameraTransform.forward, stationaryCameraRoll * cameraUp);
+ }
+ else if (camTarget != null && fmPivotMode == FMPivotModes.Target) // Rotating camera about target (we only set the position, not the rotation here as it's overridden below)
+ {
+ var pivotPoint = targetCoM ? camTarget.vessel.CoM : camTarget.transform.position; // Rotate about the part or CoM.
+ if (fmMovementModified) // Rotation axes aligned with target vessel's axes
+ {
+ var pivotUpAxis = -camTarget.vessel.ReferenceTransform.forward;
+ var rotationAdjustment = Quaternion.AngleAxis(angle.y, Vector3.Cross(pivotUpAxis, cameraTransform.forward)) * Quaternion.AngleAxis(angle.x, pivotUpAxis);
+ cameraTransform.position = pivotPoint + rotationAdjustment * (cameraTransform.position - pivotPoint);
+ upAxis = rotationAdjustment * upAxis;
+ }
+ else // Rotation axes aligned with camera's axes
+ {
+ var rotationAdjustment = Quaternion.AngleAxis(angle.x, Vector3.up) * Quaternion.AngleAxis(angle.y, Vector3.right);
+ var parentRotation = camTarget.vessel.transform.rotation;
+ var invParentRotation = Quaternion.Inverse(parentRotation);
+ var localRotation = invParentRotation * cameraTransform.rotation;
+ var localPosition = invParentRotation * (cameraTransform.position - pivotPoint);
+ localPosition = localRotation * rotationAdjustment * Quaternion.Inverse(localRotation) * localPosition;
+ cameraTransform.position = pivotPoint + parentRotation * localPosition;
+ }
+ }
+ }
+ if (Input.GetKey(KeyCode.Mouse2))
+ {
+ manualPosition += rightAxis * Input.GetAxis("Mouse X") * 2f;
+ manualPosition += forwardAxis * Input.GetAxis("Mouse Y") * 2f;
+ }
+ }
+ manualPosition += upAxis * 10 * Input.GetAxis("Mouse ScrollWheel");
+
+ // Set camera rotation if we have a target.
+ if (camTarget != null)
+ {
+ Vector3 lookPosition = camTarget.transform.position;
+ if (targetCoM)
+ {
+ lookPosition = camTarget.vessel.CoM;
+ }
+
+ cameraTransform.rotation = Quaternion.LookRotation(lookPosition - cameraTransform.position, upAxis);
+ lastTargetPosition = lookPosition;
+ }
+ else if (hasTarget)
+ {
+ cameraTransform.rotation = Quaternion.LookRotation(lastTargetPosition - cameraTransform.position, upAxis);
+ }
+
+ //autoFov
+ if (camTarget != null && autoFOV)
+ {
+ float cameraDistance = Vector3.Distance(camTarget.transform.position, cameraTransform.position);
+ float targetFoV = Mathf.Clamp((7000 / (cameraDistance + 100)) - 14 + autoZoomMargin, 2, 60);
+ //flightCamera.SetFoV(targetFoV);
+ manualFOV = targetFoV;
+ }
+ //FOV
+ if (!autoFOV)
+ {
+ zoomFactor = Mathf.Exp(zoomExp) / Mathf.Exp(1);
+ manualFOV = 60 / zoomFactor;
+ updateFOV = (currentFOV != manualFOV);
+ if (updateFOV)
+ {
+ currentFOV = Mathf.Lerp(currentFOV, manualFOV, 0.1f);
+ flightCamera.SetFoV(currentFOV);
+ updateFOV = false;
+ }
+ }
+ else
+ {
+ currentFOV = Mathf.Lerp(currentFOV, manualFOV, 0.1f);
+ flightCamera.SetFoV(currentFOV);
+ zoomFactor = 60 / currentFOV;
+ }
+
+ //vessel camera shake
+ if (shakeMultiplier > 0)
+ {
+ foreach (var v in FlightGlobals.Vessels)
+ {
+ if (!v || !v.loaded || v.packed) continue;
+ VesselCameraShake(v);
+ }
+ }
+ }
+ #endregion
+
+ #region Pathing Camera
+ void StartPathingCam()
+ {
+ toolMode = ToolModes.Pathing;
+ if (selectedPathIndex < 0 || currentPath.keyframeCount <= 0)
+ {
+ if (DEBUG) Debug.Log("[CameraTools]: Unable to start pathing camera due to no valid paths.");
+ RevertCamera();
+ return;
+ }
+ if (FlightGlobals.ActiveVessel == null)
+ {
+ Debug.LogWarning("[CameraTools]: Unable to start pathing camera due to no active vessel.");
+ RevertCamera();
+ return;
+ }
+ if (DEBUG)
+ {
+ message = "Starting pathing camera.";
+ Debug.Log("[CameraTools]: " + message);
+ DebugLog(message);
+ }
+ hasDied = false;
+ vessel = FlightGlobals.ActiveVessel;
+ cameraUp = vessel.up;
+ if (FlightCamera.fetch.mode == FlightCamera.Modes.ORBITAL || (FlightCamera.fetch.mode == FlightCamera.Modes.AUTO && FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.ORBITAL))
+ {
+ cameraUp = Vector3.up;
+ }
+ pathingLerpRate = Mathf.Pow(10, -2f * currentPath.secondarySmoothing);
+
+ SetCameraParent(vessel.transform); // Use the active vessel's transform without CoM centering as the reference for the parent transform.
+ cameraToolActive = true;
+ }
+
+ void UpdatePathingCam()
+ {
+ if (!currentPath.isGeoSpatial) // Don't update the cameraParent if using geospatial pathing.
+ {
+ cameraParent.transform.position = vessel.transform.position;
+ cameraParent.transform.rotation = vessel.transform.rotation;
+ }
+
+ if (isPlayingPath)
+ {
+ CameraTransformation tf = currentPath.Evaluate(pathTime * currentPath.timeScale);
+ if (currentPath.isGeoSpatial)
+ {
+ flightCamera.transform.position = Vector3.Lerp(flightCamera.transform.position, FlightGlobals.currentMainBody.GetWorldSurfacePosition(tf.position.x, tf.position.y, tf.position.z), pathingLerpRate);
+ flightCamera.transform.rotation = Quaternion.Slerp(flightCamera.transform.rotation, tf.rotation, pathingLerpRate);
+ }
+ else
+ {
+ flightCamera.transform.localPosition = Vector3.Lerp(flightCamera.transform.localPosition, tf.position, pathingLerpRate);
+ flightCamera.transform.localRotation = Quaternion.Slerp(flightCamera.transform.localRotation, tf.rotation, pathingLerpRate);
+ }
+ zoomExp = Mathf.Lerp(zoomExp, tf.zoom, pathingLerpRate);
+ }
+ else
+ {
+ //move
+ //mouse panning, moving
+ bool fmMovementModified = Input.GetKey(fmMovementModifier);
+ if (fmMovementModified)
+ {
+ // Note: The forwardAxis and rightAxis are reversed as this is more convenient when viewing the vessel from the front (which is a more typical use-case).
+ upAxis = -vessel.ReferenceTransform.forward;
+ forwardAxis = -vessel.ReferenceTransform.up;
+ rightAxis = -vessel.ReferenceTransform.right;
+ }
+ else
+ {
+ upAxis = flightCamera.transform.up;
+ forwardAxis = flightCamera.transform.forward;
+ rightAxis = flightCamera.transform.right;
+ }
+
+ if (enableKeypad && !boundThisFrame)
+ {
+ switch (fmMode)
+ {
+ case FMModeTypes.Position:
+ {
+ if (Input.GetKey(fmUpKey))
+ {
+ flightCamera.transform.position += upAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmDownKey))
+ {
+ flightCamera.transform.position -= upAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmForwardKey))
+ {
+ flightCamera.transform.position += forwardAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmBackKey))
+ {
+ flightCamera.transform.position -= forwardAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmLeftKey))
+ {
+ flightCamera.transform.position -= rightAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmRightKey))
+ {
+ flightCamera.transform.position += rightAxis * freeMoveSpeed * Time.fixedDeltaTime;
+ }
+
+ //keyZoom Note: pathing doesn't use autoZoomMargin
+ if (Input.GetKey(fmZoomInKey))
+ {
+ zoomExp = Mathf.Clamp(zoomExp + (keyZoomSpeed * Time.fixedDeltaTime), 1, zoomMaxExp);
+ if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1);
+ }
+ else if (Input.GetKey(fmZoomOutKey))
+ {
+ zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, zoomMaxExp);
+ if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1);
+ }
+ }
+ break;
+ case FMModeTypes.Speed:
+ {
+ if (Input.GetKey(fmUpKey))
+ {
+ fmSpeeds.y += freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmDownKey))
+ {
+ fmSpeeds.y -= freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmForwardKey))
+ {
+ fmSpeeds.z += freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmBackKey))
+ {
+ fmSpeeds.z -= freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmLeftKey))
+ {
+ fmSpeeds.x -= freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmRightKey))
+ {
+ fmSpeeds.x += freeMoveSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ if (Input.GetKey(fmZoomInKey))
+ {
+ fmSpeeds.w += keyZoomSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ else if (Input.GetKey(fmZoomOutKey))
+ {
+ fmSpeeds.w -= keyZoomSpeed * Time.fixedDeltaTime * Time.fixedDeltaTime;
+ }
+ }
+ break;
+ }
+ }
+
+ if (Input.GetKey(KeyCode.Mouse1) && Input.GetKey(KeyCode.Mouse2)) // Middle & right: tilt left/right
+ {
+ flightCamera.transform.rotation = Quaternion.AngleAxis(Input.GetAxis("Mouse X") * -2f, flightCamera.transform.forward) * flightCamera.transform.rotation;
+ }
+ else if (Input.GetKey(KeyCode.Mouse0) && Input.GetKey(KeyCode.Mouse2)) // Left & middle: move up/down
+ {
+ flightCamera.transform.position += upAxis * Input.GetAxis("Mouse Y") * 2f;
+ }
+ else
+ {
+ Vector2 angle = Input.GetKey(KeyCode.Mouse1) ? new(Input.GetAxis("Mouse X") * 2f / (zoomExp * zoomExp), -Input.GetAxis("Mouse Y") * 2f / (zoomExp * zoomExp)) : default;
+ angle += GetControllerInput(scale: 2f / (zoomExp * zoomExp), inverted: fmPivotMode == FMPivotModes.Camera); // Controller input: .x => hdg, .y => pitch
+ if (angle != default) // Right: rotate (pitch/yaw) around the pivot
+ {
+ if (fmMovementModified) // Rotation axes aligned with target axes.
+ {
+ var rotationAdjustment = Quaternion.AngleAxis(angle.y, Vector3.Cross(upAxis, flightCamera.transform.forward)) * Quaternion.AngleAxis(angle.x, upAxis);
+ if (fmPivotMode == FMPivotModes.Target) flightCamera.transform.position = cameraParent.transform.position + rotationAdjustment * (flightCamera.transform.position - cameraParent.transform.position);
+ flightCamera.transform.rotation = rotationAdjustment * flightCamera.transform.rotation;
+ }
+ else // Rotation axes aligned with camera axes.
+ {
+ var rotationAdjustment = Quaternion.AngleAxis(angle.x, Vector3.up) * Quaternion.AngleAxis(angle.y, Vector3.right);
+ if (fmPivotMode == FMPivotModes.Target)
+ {
+ var localRotation = flightCamera.transform.localRotation;
+ flightCamera.transform.localPosition = localRotation * rotationAdjustment * Quaternion.Inverse(localRotation) * flightCamera.transform.localPosition;
+ }
+ flightCamera.transform.rotation *= rotationAdjustment;
+ }
+ }
+ if (Input.GetKey(KeyCode.Mouse2)) // Middle: move left/right and forward/backward
+ {
+ flightCamera.transform.position += rightAxis * Input.GetAxis("Mouse X") * 2f;
+ flightCamera.transform.position += forwardAxis * Input.GetAxis("Mouse Y") * 2f;
+ }
+ }
+ if (freeMoveSpeedRaw != (freeMoveSpeedRaw = Mathf.Clamp(freeMoveSpeedRaw + 0.5f * Input.GetAxis("Mouse ScrollWheel"), freeMoveSpeedMinRaw, freeMoveSpeedMaxRaw)))
+ {
+ freeMoveSpeed = Mathf.Pow(10f, freeMoveSpeedRaw);
+ if (textInput) inputFields["freeMoveSpeed"].currentValue = freeMoveSpeed;
+ }
+ }
+
+ //zoom
+ zoomFactor = Mathf.Exp(zoomExp) / Mathf.Exp(1);
+ manualFOV = 60 / zoomFactor;
+ updateFOV = (currentFOV != manualFOV);
+ if (updateFOV)
+ {
+ currentFOV = Mathf.Lerp(currentFOV, manualFOV, 0.1f);
+ flightCamera.SetFoV(currentFOV);
+ updateFOV = false;
+ }
+ }
+
+ void CreateNewPath()
+ {
+ showKeyframeEditor = false;
+ availablePaths.Add(new CameraPath());
+ selectedPathIndex = availablePaths.Count - 1;
+ if (isPlayingPath) StopPlayingPath();
+ }
+
+ void DeletePath(int index)
+ {
+ if (index < 0) return;
+ if (index >= availablePaths.Count) return;
+ availablePaths.RemoveAt(index);
+ if (index <= selectedPathIndex) { --selectedPathIndex; }
+ if (isPlayingPath) StopPlayingPath();
+ }
+
+ void SelectPath(int index)
+ {
+ selectedPathIndex = index;
+ }
+
+ void SelectKeyframe(int index)
+ {
+ if (isPlayingPath)
+ {
+ StopPlayingPath();
+ }
+ currentKeyframeIndex = index;
+ UpdateCurrentValues();
+ showKeyframeEditor = true;
+ ViewKeyframe(currentKeyframeIndex);
+ }
+
+ void DeselectKeyframe()
+ {
+ currentKeyframeIndex = -1;
+ showKeyframeEditor = false;
+ }
+
+ void DeleteKeyframe(int index)
+ {
+ currentPath.RemoveKeyframe(index);
+ if (index == currentKeyframeIndex)
+ {
+ DeselectKeyframe();
+ }
+ if (currentPath.keyframeCount > 0 && currentKeyframeIndex >= 0)
+ {
+ SelectKeyframe(Mathf.Clamp(currentKeyframeIndex, 0, currentPath.keyframeCount - 1));
+ }
+ else
+ {
+ if (isPlayingPath) StopPlayingPath();
+ }
+ }
+
+ void UpdateCurrentValues()
+ {
+ if (currentPath == null) return;
+ if (currentKeyframeIndex < 0 || currentKeyframeIndex >= currentPath.keyframeCount)
+ {
+ return;
+ }
+ CameraKeyframe currentKey = currentPath.GetKeyframe(currentKeyframeIndex);
+ currentKeyframeTime = currentKey.time;
+ currentKeyframePositionInterpolationType = currentKey.positionInterpolationType;
+ currentKeyframeRotationInterpolationType = currentKey.rotationInterpolationType;
+
+ currKeyTimeString = currentKeyframeTime.ToString();
+ }
+
+ void CreateNewKeyframe()
+ {
+ if (FlightGlobals.ActiveVessel == null)
+ {
+ Debug.LogWarning("[CameraTools]: Unable to create new pathing keyframe without an active vessel.");
+ }
+ showPathSelectorWindow = false;
+
+ float time = 0;
+ PositionInterpolationType positionInterpolationType = PositionInterpolationType.CubicSpline;
+ RotationInterpolationType rotationInterpolationType = RotationInterpolationType.CubicSpline;
+ if (currentPath.keyframeCount > 0)
+ {
+ CameraKeyframe previousKeyframe = currentPath.GetKeyframe(currentPath.keyframeCount - 1);
+ positionInterpolationType = previousKeyframe.positionInterpolationType;
+ rotationInterpolationType = previousKeyframe.rotationInterpolationType;
+
+ if (isPlayingPath)
+ {
+ time = pathTime * currentPath.timeScale;
+ }
+ else
+ {
+ time = previousKeyframe.time + 10;
+ }
+ }
+
+ if (!cameraToolActive)
+ {
+ if (flightCamera.FieldOfView != flightCamera.fovDefault)
+ {
+ zoomFactor = 60 / flightCamera.FieldOfView;
+ zoomExp = Mathf.Log(zoomFactor) + 1f;
+ }
+
+ if (!cameraParentWasStolen)
+ SaveOriginalCamera();
+ SetCameraParent(FlightGlobals.ActiveVessel.transform);
+ cameraToolActive = true;
+ }
+
+ currentPath.AddTransform(flightCamera.transform, zoomExp, ref time, positionInterpolationType, rotationInterpolationType);
+
+ SelectKeyframe(currentPath.times.IndexOf(time));
+
+ if (currentPath.keyframeCount > 6)
+ {
+ keysScrollPos.y += entryHeight;
+ }
+ }
+
+ void ViewKeyframe(int index)
+ {
+ if (!cameraToolActive)
+ {
+ StartPathingCam();
+ }
+ CameraKeyframe currentKey = currentPath.GetKeyframe(index);
+ if (currentPath.isGeoSpatial)
+ {
+ flightCamera.transform.position = FlightGlobals.currentMainBody.GetWorldSurfacePosition(currentKey.position.x, currentKey.position.y, currentKey.position.z);
+ flightCamera.transform.rotation = currentKey.rotation;
+ }
+ else
+ {
+ flightCamera.transform.localPosition = currentKey.position;
+ flightCamera.transform.localRotation = currentKey.rotation;
+ }
+ SetZoomImmediate(currentKey.zoom);
+ }
+
+ void PlayPathingCam()
+ {
+ if (DEBUG)
+ {
+ message = "Playing pathing camera.";
+ Debug.Log("[CameraTools]: " + message);
+ DebugLog(message);
+ }
+ if (selectedPathIndex < 0)
+ {
+ if (DEBUG) Debug.Log("[CameraTools]: selectedPathIndex < 0, reverting.");
+ RevertCamera();
+ return;
+ }
+
+ if (currentPath.keyframeCount <= 0)
+ {
+ if (DEBUG) Debug.Log("[CameraTools]: keyframeCount <= 0, reverting.");
+ RevertCamera();
+ return;
+ }
+
+ float startTime = 0;
+ if (currentKeyframeIndex > -1)
+ {
+ startTime = currentPath.GetKeyframe(currentKeyframeIndex).time;
+ }
+
+ DeselectKeyframe();
+
+ if (!cameraToolActive)
+ {
+ StartPathingCam();
+ }
+
+ CameraTransformation firstFrame = currentPath.Evaluate(startTime);
+ if (currentPath.isGeoSpatial)
+ {
+ flightCamera.transform.position = FlightGlobals.currentMainBody.GetWorldSurfacePosition(firstFrame.position.x, firstFrame.position.y, firstFrame.position.z);
+ flightCamera.transform.rotation = firstFrame.rotation;
+ }
+ else
+ {
+ flightCamera.transform.localPosition = firstFrame.position;
+ flightCamera.transform.localRotation = firstFrame.rotation;
+ }
+ SetZoomImmediate(firstFrame.zoom);
+
+ isPlayingPath = true;
+ pathStartTime = GetTime() - (startTime / currentPath.timeScale);
+ }
+
+ void StopPlayingPath()
+ {
+ isPlayingPath = false;
+ }
+
+ void TogglePathList()
+ {
+ showKeyframeEditor = false;
+ showPathSelectorWindow = !showPathSelectorWindow;
+ }
+
+ ///
+ /// Convert the current pathing view to a stationary camera view.
+ ///
+ void FromPathingToStationary()
+ {
+ // Clear a bunch of stuff to make sure it's not going to automatically do something else in stationary camera mode.
+ camTarget = null;
+ randomMode = false;
+ autoFlybyPosition = false;
+ manualOffset = false;
+ setPresetOffset = false;
+ autoFOV = false;
+ hasSavedRotation = false;
+ StartStationaryCamera();
+ // Also, close any pathing windows that might be open.
+ showPathSelectorWindow = false;
+ showKeyframeEditor = false;
+ }
+ #endregion
+
+ #region Shake
+ public void ShakeCamera(float magnitude)
+ {
+ shakeMagnitude = Mathf.Max(shakeMagnitude, magnitude);
+ }
+
+ void UpdateCameraShake()
+ {
+ if (shakeMultiplier > 0)
+ {
+ if (shakeMagnitude > 0.1f)
+ {
+ Vector3 shakeAxis = UnityEngine.Random.onUnitSphere;
+ shakeOffset = Mathf.Sin(shakeMagnitude * 20 * Time.time) * (shakeMagnitude / 10) * shakeAxis;
+ }
+
+ flightCamera.transform.rotation = Quaternion.AngleAxis((shakeMultiplier / 2) * shakeMagnitude / 50f, Vector3.ProjectOnPlane(UnityEngine.Random.onUnitSphere, flightCamera.transform.forward)) * flightCamera.transform.rotation;
+ }
+
+ shakeMagnitude = Mathf.Lerp(shakeMagnitude, 0, 0.1f);
+ }
+
+ public void VesselCameraShake(Vessel vessel)
+ {
+ if (vessel.vesselType == VesselType.Debris) return; // Ignore debris
- public void VesselCameraShake(Vessel vessel)
- {
//shake
float camDistance = Vector3.Distance(flightCamera.transform.position, vessel.CoM);
@@ -879,18 +2471,18 @@ public void VesselCameraShake(Vessel vessel)
float atmosphericFactor = (float)vessel.dynamicPressurekPa / 2f;
- float angleToCam = Vector3.Angle(vessel.srf_velocity, FlightCamera.fetch.mainCamera.transform.position - vessel.transform.position);
+ float angleToCam = Vector3.Angle(vessel.Velocity(), FlightCamera.fetch.mainCamera.transform.position - vessel.CoM);
angleToCam = Mathf.Clamp(angleToCam, 1, 180);
float srfSpeed = (float)vessel.srfSpeed;
- float lagAudioFactor = (75000 / (Vector3.Distance(vessel.transform.position, FlightCamera.fetch.mainCamera.transform.position) * srfSpeed * angleToCam / 90));
+ float lagAudioFactor = (75000 / (Vector3.Distance(vessel.CoM, FlightCamera.fetch.mainCamera.transform.position) * srfSpeed * angleToCam / 90));
lagAudioFactor = Mathf.Clamp(lagAudioFactor * lagAudioFactor * lagAudioFactor, 0, 4);
lagAudioFactor += srfSpeed / 230;
float waveFrontFactor = ((3.67f * angleToCam) / srfSpeed);
waveFrontFactor = Mathf.Clamp(waveFrontFactor * waveFrontFactor * waveFrontFactor, 0, 2);
- if(vessel.srfSpeed > 330)
+ if (vessel.srfSpeed > 330)
{
waveFrontFactor = (srfSpeed / (angleToCam) < 3.67f) ? srfSpeed / 15 : 0;
}
@@ -909,66 +2501,75 @@ public void VesselCameraShake(Vessel vessel)
float GetTotalThrust()
{
float total = 0;
- foreach(var engine in vessel.FindPartModulesImplementing())
- {
- total += engine.finalThrust;
- }
+ using (var engine = engines.GetEnumerator())
+ while (engine.MoveNext())
+ {
+ if (engine.Current == null) continue;
+ total += engine.Current.finalThrust;
+ }
return total;
}
+ #endregion
+ #region Atmospherics
void AddAtmoAudioControllers(bool includeActiveVessel)
{
- if(!useAudioEffects)
- {
- return;
- }
+ if (!useAudioEffects) return;
- foreach(var vessel in FlightGlobals.Vessels)
+ foreach (var vessel in FlightGlobals.Vessels)
{
- if(!vessel || !vessel.loaded || vessel.packed || (!includeActiveVessel && vessel.isActiveVessel))
+ if (!vessel || !vessel.loaded || vessel.packed || (!includeActiveVessel && vessel.isActiveVessel))
{
continue;
}
+ if (ignoreVesselTypesForAudio.Contains(vessel.vesselType)) continue;
- vessel.gameObject.AddComponent();
+ vessel.gameObject.AddComponent(); // Always add, since they get removed when OnResetCTools triggers.
}
}
-
+
void SetDoppler(bool includeActiveVessel)
{
- if(hasSetDoppler)
- {
- return;
- }
+ if (hasSetDoppler || !useAudioEffects || !hasSpatializerPlugin) return;
- if(!useAudioEffects)
- {
- return;
- }
+ // Debug.Log($"DEBUG Setting doppler");
+ // Debug.Log($"DEBUG audio spatializer: {AudioSettings.GetSpatializerPluginName()}"); // This is an empty string, so doppler effects using Unity's built-in settings are not available.
+ // Manually handling doppler effects won't work either as there's no events for newly added audioSources and no way to check when the pitch is adjusted for other reasons.
audioSources = FindObjectsOfType();
- originalAudioSourceDoppler = new float[audioSources.Length];
+ // if (DEBUG) Debug.Log("CameraTools.DEBUG audioSources: " + string.Join(", ", audioSources.Select(a => a.name)));
+ originalAudioSourceSettings.Clear();
- for(int i = 0; i < audioSources.Length; i++)
+ for (int i = 0; i < audioSources.Length; i++)
{
- originalAudioSourceDoppler[i] = audioSources[i].dopplerLevel;
+ if (excludeAudioSources.Contains(audioSources[i].name)) continue;
- if(!includeActiveVessel)
+ if (!includeActiveVessel)
{
Part p = audioSources[i].GetComponentInParent();
- if(p && p.vessel.isActiveVessel) continue;
+ if (p && p.vessel.isActiveVessel) continue;
}
- audioSources[i].dopplerLevel = 1;
- audioSources[i].velocityUpdateMode = AudioVelocityUpdateMode.Fixed;
- audioSources[i].bypassEffects = false;
- audioSources[i].spatialBlend = 1;
-
- if(audioSources[i].gameObject.GetComponentInParent())
+ var part = audioSources[i].gameObject.GetComponentInParent();
+ if (part != null)
+ {
+ if (part.vessel != null && !ignoreVesselTypesForAudio.Contains(part.vessel.vesselType))
+ {
+ var pa = audioSources[i].gameObject.AddComponent(); // Always add, since they get removed when OnResetCTools triggers.
+ pa.audioSource = audioSources[i];
+ pa.StoreOriginalSettings();
+ pa.ApplyEffects();
+ // if (DEBUG && audioSources[i].isPlaying) Debug.Log($"CameraTools.DEBUG adding part audio controller for {part} on {part.vessel.vesselName} for audiosource {i} ({audioSources[i].name}) with priority: {audioSources[i].priority}, doppler level {audioSources[i].dopplerLevel}, rollOff: {audioSources[i].rolloffMode}, spatialize: {audioSources[i].spatialize}, spatial blend: {audioSources[i].spatialBlend}, min/max dist:{audioSources[i].minDistance}/{audioSources[i].maxDistance}, clip: {audioSources[i].clip?.name}, output group: {audioSources[i].outputAudioMixerGroup}");
+ }
+ }
+ else // Set/reset part audio separately from others.
{
- //Debug.Log("Added CTPartAudioController to :" + audioSources[i].name);
- CTPartAudioController pa = audioSources[i].gameObject.AddComponent();
- pa.audioSource = audioSources[i];
+ originalAudioSourceSettings.Add((i, audioSources[i].dopplerLevel, audioSources[i].velocityUpdateMode, audioSources[i].bypassEffects, audioSources[i].spatialize, audioSources[i].spatialBlend));
+ audioSources[i].dopplerLevel = 1;
+ audioSources[i].velocityUpdateMode = AudioVelocityUpdateMode.Fixed;
+ audioSources[i].bypassEffects = false;
+ audioSources[i].spatialize = true;
+ audioSources[i].spatialBlend = 1;
}
}
@@ -977,373 +2578,657 @@ void SetDoppler(bool includeActiveVessel)
void ResetDoppler()
{
- if(!hasSetDoppler)
+ if (!hasSetDoppler) return;
+
+ foreach (var (index, dopplerLevel, velocityUpdateMode, bypassEffects, spatialize, spatialBlend) in originalAudioSourceSettings) // Set/reset part audio separately from others.
+ {
+ if (audioSources[index] == null) continue;
+ audioSources[index].dopplerLevel = dopplerLevel;
+ audioSources[index].velocityUpdateMode = velocityUpdateMode;
+ audioSources[index].bypassEffects = bypassEffects;
+ audioSources[index].spatialBlend = spatialBlend;
+ audioSources[index].spatialize = spatialize;
+ }
+ for (int i = 0; i < audioSources.Length; i++)
+ {
+ if (audioSources[i] == null || excludeAudioSources.Contains(audioSources[i].name)) continue;
+ CTPartAudioController pa = audioSources[i].gameObject.GetComponent();
+ if (pa == null) continue;
+ pa.RestoreOriginalSettings();
+ }
+
+ hasSetDoppler = false;
+ }
+ #endregion
+
+ #region Revert/Reset
+ void SwitchToVessel(Vessel v)
+ {
+ if (v == null)
{
+ RevertCamera();
return;
}
+ if (DEBUG)
+ {
+ message = "Switching to vessel " + v.vesselName;
+ Debug.Log("[CameraTools]: " + message);
+ DebugLog(message);
+ }
+ vessel = v;
+ vesselRadius = vessel.vesselSize.magnitude / 2;
+ // Switch to a usable camera mode if necessary.
+ if (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA)
+ {
+ CameraManager.Instance.SetCameraFlight();
+ }
+ if (toolMode == ToolModes.DogfightCamera)
+ {
+ camTarget = null;
+ chasePlaneTargetIsEVA = vessel.isEVA;
+ }
+ cockpitView = false;
+ cockpits.Clear();
+ engines.Clear();
+ hasDied = false;
+
+ if (BDArmory.hasBDA)
+ {
+ bdArmory.CheckForBDAI(v);
+ bdArmory.CheckForBDWM(v);
+ if (!bdArmory.hasBDAI) bdArmory.CheckForBDMissile(v);
+ bdArmory.UpdateAIDogfightTarget(true);
+ }
+ if (cameraToolActive)
+ {
+ if (BDArmory.IsInhibited) RevertCamera();
+ else if (randomMode)
+ {
+ ChooseRandomMode();
+ }
+ else
+ {
+ switchToMode = toolMode; // Don't switch modes.
+ }
+
+ engines = vessel.FindPartModulesImplementing();
+ vesselSwitched = true;
+ }
+ }
- for(int i = 0; i < audioSources.Length; i++)
+ public ToolModes ChooseRandomMode()
+ {
+ // Actual switching is delayed until the LateUpdate to avoid a flicker.
+ var randomModeOverride = bdArmory.hasPilotAI && bdArmory.aiType == BDArmory.AIType.Pilot && (
+ vessel.LandedOrSplashed ||
+ (vessel.radarAltitude - vesselRadius < 20 && vessel.verticalSpeed > 0) // Taking off.
+ );
+ var stationarySurfaceVessel = (vessel.Landed && vessel.Speed() < 1) || (vessel.Splashed && vessel.Speed() < 5); // Land or water vessel that isn't moving much.
+ if (stationarySurfaceVessel || randomModeOverride)
+ {
+ switchToMode = ToolModes.StationaryCamera;
+ }
+ else if (BDArmory.hasBDA && bdArmory.isBDMissile)
{
- if(audioSources[i] != null)
+ switchToMode = ToolModes.DogfightCamera; // Use dogfight chase mode for BDA missiles.
+ }
+ else
+ {
+ cockpits.Clear();
+ var rand = rng.Next(100);
+ if (rand < randomModeDogfightChance)
+ {
+ switchToMode = ToolModes.DogfightCamera;
+ }
+ else if (rand < randomModeDogfightChance + randomModeIVAChance)
+ {
+ switchToMode = ToolModes.DogfightCamera;
+ cockpits = vessel.FindPartModulesImplementing();
+ if (cockpits.Any(c => c.part.protoModuleCrew.Count > 0))
+ { cockpitView = true; }
+ }
+ else if (rand < randomModeDogfightChance + randomModeIVAChance + randomModeStationaryChance)
+ {
+ switchToMode = ToolModes.StationaryCamera;
+ }
+ else
{
- audioSources[i].dopplerLevel = originalAudioSourceDoppler[i];
- audioSources[i].velocityUpdateMode = AudioVelocityUpdateMode.Auto;
+ switchToMode = ToolModes.Pathing;
}
}
+ return switchToMode;
+ }
-
+ public void RevertCamera()
+ {
+ if (!(CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.Flight || CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA)) // Don't revert if not in Flight or IVA mode, it's already been deactivated, but the flight camera isn't available to be reconfigured.
+ {
+ revertWhenInFlightMode = true;
+ activateWhenInFlightMode = false;
+ return;
+ }
+ revertWhenInFlightMode = false;
+ if (DEBUG)
+ {
+ message = $"Reverting camera from {toolMode}.";
+ Debug.Log($"[CameraTools]: {message}");
+ DebugLog(message);
+ }
+ if (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA) // If we were in IVA mode, go back to Flight mode and pretend we were active.
+ {
+ CameraManager.Instance.SetCameraFlight();
+ cameraToolActive = true;
+ }
- hasSetDoppler = false;
+ if (cameraToolActive)
+ {
+ presetOffset = flightCamera.transform.position;
+ if (camTarget == null && saveRotation)
+ {
+ savedRotation = flightCamera.transform.rotation;
+ hasSavedRotation = true;
+ }
+ else
+ {
+ hasSavedRotation = false;
+ }
+ }
+ hasDied = false;
+ if (FlightGlobals.ActiveVessel != null && HighLogic.LoadedScene == GameScenes.FLIGHT && flightCamera.vesselTarget != FlightGlobals.ActiveVessel)
+ {
+ flightCamera.SetTarget(FlightGlobals.ActiveVessel.transform, FlightCamera.TargetMode.Vessel);
+ }
+ if (cameraToolActive)
+ {
+ if (DEBUG) Debug.Log($"[CameraTools]: Resetting camera parent to {origParent.name}");
+ flightCamera.transform.parent = origParent;
+ if (origParent != null) // Restore the camera to the original local offsets from the original gameObject.
+ {
+ flightCamera.transform.localPosition = origLocalPosition;
+ flightCamera.transform.localRotation = origLocalRotation;
+ flightCamera.SetDistanceImmediate(BDArmory.hasBDA ? Mathf.Min(origDistance, bdArmory.restoreDistanceLimit) : origDistance);
+ }
+ else // Otherwise, restore the camera to the original absolute position and rotation as the original gameObject no longer exists (if it even existed to begin with).
+ {
+ flightCamera.transform.position = origPosition;
+ flightCamera.transform.rotation = origRotation;
+ }
+ flightCamera.mode = origMode; // Restore the camera mode. Note: using flightCamera.setModeImmediate(origMode); causes the annoying camera mode change messages to appear, simply setting the value doesn't do this and seems to work fine.
+ flightCamera.SetFoV(origFov);
+ currentFOV = origFov;
+ cameraParentWasStolen = false;
+ dogfightLastTarget = false;
+ }
+ if (HighLogic.LoadedSceneIsFlight)
+ flightCamera.mainCamera.nearClipPlane = origNearClip;
+ else
+ Camera.main.nearClipPlane = origNearClip;
+ if (BDArmory.hasBDA) bdArmory.OnRevert();
+
+ flightCamera.ActivateUpdate();
+
+ cameraToolActive = false;
+
+ StopPlayingPath();
+
+ ResetDoppler();
+
+ try
+ {
+ if (OnResetCTools != null)
+ { OnResetCTools(); }
+ }
+ catch (Exception e)
+ { Debug.Log("[CameraTools]: Caught exception resetting CameraTools " + e.ToString()); }
+
+ // Reset the parameters we set in other mods so as not to mess with them while we're not active.
+ timeControl.SetTimeControlCameraZoomFix(true);
+ betterTimeWarp.SetBetterTimeWarpScaleCameraSpeed(true);
}
-
- void StartStationaryCamera()
+ void SaveOriginalCamera()
{
- Debug.Log ("flightCamera position init: "+flightCamera.transform.position);
- if(FlightGlobals.ActiveVessel != null)
- {
- hasDied = false;
- vessel = FlightGlobals.ActiveVessel;
- cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.GetWorldPos3D()).normalized;
- if(FlightCamera.fetch.mode == FlightCamera.Modes.ORBITAL || (FlightCamera.fetch.mode == FlightCamera.Modes.AUTO && FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.ORBITAL))
+ origPosition = flightCamera.transform.position;
+ origRotation = flightCamera.transform.rotation;
+ origLocalPosition = flightCamera.transform.localPosition;
+ origLocalRotation = flightCamera.transform.localRotation;
+ origParent = flightCamera.transform.parent;
+ origNearClip = HighLogic.LoadedSceneIsFlight ? flightCamera.mainCamera.nearClipPlane : Camera.main.nearClipPlane;
+ origDistance = flightCamera.Distance;
+ origMode = flightCamera.mode;
+ origFov = flightCamera.FieldOfView;
+ if (DEBUG) Debug.Log($"[CameraTools]: Original camera saved. parent: {origParent.name}, mode: {origMode}, FOV: {origFov}, distance: {origDistance}, near: {origNearClip}");
+ }
+
+ void PostDeathRevert(GameScenes f)
+ {
+ if (cameraToolActive)
+ {
+ RevertCamera();
+ }
+ }
+
+ void PostDeathRevert(Vessel v)
+ {
+ if (cameraToolActive)
+ {
+ RevertCamera();
+ }
+ }
+
+ void ResetRoll()
+ {
+ stationaryCameraRoll = Quaternion.identity;
+ flightCamera.transform.rotation = Quaternion.LookRotation(flightCamera.transform.forward, cameraUp);
+ }
+ #endregion
+
+ #region GUI
+ //GUI
+ void OnGUI()
+ {
+ if (guiEnabled && gameUIToggle && HighLogic.LoadedSceneIsFlight)
+ {
+ if (inputFieldStyle == null) SetupInputFieldStyle();
+ if (scalingUI && Mouse.Left.GetButtonUp()) // Don't rescale the settings window until the mouse is released otherwise it messes with the slider.
{
- cameraUp = Vector3.up;
+ scalingUI = false;
+ windowRect.x += windowRect.width * (previousUIScale - UIScale);
}
+ if (scalingUI) { if (previousUIScale != 1) GUIUtility.ScaleAroundPivot(previousUIScale * Vector2.one, windowRect.position); }
+ else { if (_UIScale != 1) GUIUtility.ScaleAroundPivot(_UIScale * Vector2.one, windowRect.position); }
+ windowRect = GUI.Window(GUIUtility.GetControlID(FocusType.Passive), windowRect, GuiWindow, "");
- flightCamera.SetTargetNone();
- flightCamera.transform.parent = cameraParent.transform;
- flightCamera.DeactivateUpdate();
- cameraParent.transform.position = vessel.transform.position+vessel.rb_velocity*Time.fixedDeltaTime;
- manualPosition = Vector3.zero;
-
-
- hasTarget = (camTarget != null) ? true : false;
-
-
- Vector3 rightAxis = -Vector3.Cross(vessel.srf_velocity, vessel.upAxis).normalized;
- //Vector3 upAxis = flightCamera.transform.up;
-
+ if (showKeyframeEditor)
+ {
+ KeyframeEditorWindow();
+ }
+ if (showPathSelectorWindow)
+ {
+ PathSelectorWindow();
+ }
+ }
+ if (DEBUG)
+ {
+ if (debugMessages.Count > 0)
+ {
+ var now = Time.time;
+ debugMessages = debugMessages.Where(m => now - m.Item1 < 5f).ToList();
+ if (debugMessages.Count > 0)
+ {
+ var messages = string.Join("\n", debugMessages.Select(m => m.Item1.ToString("0.000") + " " + m.Item2));
+ GUI.Label(cShadowRect, messages, cShadowStyle);
+ GUI.Label(cDebugRect, messages, cStyle);
+ }
+ }
+ }
+ if (DEBUG2)
+ {
+ if (debug2Messages.Count > 0)
+ {
+ GUI.Label(new Rect(Screen.width - 750, 100, 700, Screen.height / 2), string.Join("\n", debug2Messages.Select(m => m.Item1.ToString("0.000") + " " + m.Item2)));
+ }
+ }
+ }
+
+ Rect LabelRect(float line)
+ { return new Rect(leftIndent, contentTop + line * entryHeight, contentWidth, entryHeight); }
+ Rect HalfRect(float line, int pos = 0)
+ { return new Rect(leftIndent + pos * contentWidth / 2f, contentTop + line * entryHeight, contentWidth / 2f, entryHeight); }
+ Rect LeftRect(float line)
+ { return new Rect(leftIndent, contentTop + line * entryHeight, windowWidth / 2f + leftIndent * 2f, entryHeight); }
+ Rect RightRect(float line)
+ { return new Rect(windowWidth / 2f + 3f * leftIndent, contentTop + line * entryHeight, contentWidth / 2f - 3f * leftIndent, entryHeight); }
+ Rect QuarterRect(float line, int quarter)
+ { return new Rect(leftIndent + quarter * contentWidth / 4f, contentTop + line * entryHeight, contentWidth / 4f, entryHeight); }
+ Rect ThinRect(float line)
+ { return new Rect(leftIndent, contentTop + line * entryHeight, contentWidth, entryHeight - 2); }
+ Rect ThinHalfRect(float line, int pos = 0)
+ { return new Rect(leftIndent + pos * (contentWidth / 2f + 2f), contentTop + line * entryHeight, contentWidth / 2 - 2, entryHeight - 2); }
+ Rect SliderLabelLeft(float line, float indent)
+ { return new Rect(leftIndent, contentTop + line * entryHeight, indent, entryHeight); }
+ Rect SliderLabelRight(float line, float widthAdjust = 0)
+ { return new Rect(leftIndent + contentWidth - 30f - widthAdjust, contentTop + line * entryHeight, 30f + widthAdjust, entryHeight); }
+ Rect SliderRect(float line, float indent, float widthAdjust = 0)
+ { return new Rect(leftIndent + indent, contentTop + line * entryHeight + 6f, contentWidth - indent - 35f + widthAdjust, entryHeight); }
+ Rect RightSliderRect(float line)
+ { return new Rect(windowWidth / 2f + 3f * leftIndent, contentTop + line * entryHeight + 6f, contentWidth / 2f - 3f * leftIndent, entryHeight); }
+ void SetupInputFieldStyle()
+ {
+ inputFieldStyle = new GUIStyle(GUI.skin.textField);
+ inputFieldStyle.alignment = TextAnchor.UpperRight;
+ }
+ void GuiWindow(int windowID)
+ {
+ GUI.DragWindow(new Rect(0, 0, windowWidth, draggableHeight));
+
+ GUI.Label(new Rect(0, contentTop, windowWidth, 40), LocalizeStr("Title"), titleStyle);
+ GUI.Label(new Rect(windowWidth / 2f, contentTop + 35f, windowWidth / 2f - leftIndent - entryHeight, entryHeight), Localize("Version", Version), watermarkStyle);
+ if (GUI.Toggle(new Rect(windowWidth - leftIndent - 14f, contentTop + 31f, 20f, 20f), cameraToolActive, "") != cameraToolActive)
+ {
+ if (cameraToolActive)
+ {
+ autoEnableOverriden = true;
+ RevertCamera();
+ }
+ else
+ {
+ autoEnableOverriden = false;
+ if (randomMode)
+ {
+ ChooseRandomMode();
+ }
+ CameraActivate();
+ }
+ }
+
+ float line = 1.75f;
+ float parseResult;
+
+ //tool mode switcher
+ GUI.Label(LabelRect(++line), Localize("Tool", toolMode.ToString()), leftLabelBold);
+ if (GUI.Button(new Rect(leftIndent, contentTop + (++line * entryHeight), 25, entryHeight - 2), Localize("PrevMode")))
+ {
+ CycleToolMode(false);
+ if (cameraToolActive) CameraActivate();
+ }
+ if (GUI.Button(new Rect(leftIndent + 25 + 4, contentTop + (line * entryHeight), 25, entryHeight - 2), Localize("NextMode")))
+ {
+ CycleToolMode(true);
+ if (cameraToolActive) CameraActivate();
+ }
+ if (GUI.Button(new Rect(windowWidth - leftIndent - 54, contentTop + (line * entryHeight), 25, entryHeight - 2), Localize("ShowTooltips"), ShowTooltips ? GUI.skin.box : GUI.skin.button))
+ {
+ ShowTooltips = !ShowTooltips;
+ }
+ if (GUI.Button(new Rect(windowWidth - leftIndent - 25, contentTop + (line * entryHeight), 25, entryHeight - 2), Localize("ToggleTextFields"), textInput ? GUI.skin.box : GUI.skin.button))
+ {
+ textInput = !textInput;
+ if (!textInput) // Set the fields to their currently showing values.
+ {
+ foreach (var field in inputFields.Keys)
+ {
+ inputFields[field].tryParseValueNow();
+ var fieldInfo = typeof(CamTools).GetField(field);
+ if (fieldInfo != null) { fieldInfo.SetValue(this, inputFields[field].currentValue); }
+ else
+ {
+ var propInfo = typeof(CamTools).GetProperty(field);
+ propInfo.SetValue(this, inputFields[field].currentValue);
+ }
+ }
+ if (currentPath != null)
+ {
+ currentPath.secondarySmoothing = pathingSecondarySmoothing;
+ currentPath.timeScale = pathingTimeScale;
+ }
+ freeMoveSpeedRaw = Mathf.Log10(freeMoveSpeed);
+ zoomSpeedRaw = Mathf.Log10(keyZoomSpeed);
+ }
+ else // Set the input fields to their current values.
+ {
+ if (currentPath != null)
+ {
+ pathingSecondarySmoothing = currentPath.secondarySmoothing;
+ pathingTimeScale = currentPath.timeScale;
+ }
+ foreach (var field in inputFields.Keys)
+ {
+ var fieldInfo = typeof(CamTools).GetField(field);
+ if (fieldInfo != null) { inputFields[field].currentValue = (float)fieldInfo.GetValue(this); }
+ else
+ {
+ var propInfo = typeof(CamTools).GetProperty(field);
+ inputFields[field].currentValue = (float)propInfo.GetValue(this);
+ }
+ }
+ if (DEBUG && fmMode == FMModeTypes.Speed) DebugLog("Disabling speed free move mode due to switching to numeric inputs.");
+ fmMode = FMModeTypes.Position; // Disable speed free move mode when using numeric inputs.
+ }
+ if (BDArmory.hasBDA) bdArmory.ToggleInputFields(textInput);
+ }
+
+ ++line;
+ useAudioEffects = GUI.Toggle(LabelRect(++line), useAudioEffects, Localize("AudioEffects"));
+ if (enableVFX != (enableVFX = GUI.Toggle(LabelRect(++line), enableVFX, Localize("VisualEffects"))))
+ {
+ if (cameraToolActive) origParent.position = enableVFX ? cameraParent.transform.position : FlightGlobals.currentMainBody.position; // Set the origParent to the centre of the current mainbody to make sure it's out of range for FX to take effect.
+ }
+ if (BDArmory.hasBDA) bdArmory.autoEnableForBDA = GUI.Toggle(LabelRect(++line), bdArmory.autoEnableForBDA, Localize("AutoEnableForBDA"));
+ randomMode = GUI.Toggle(ThinRect(++line), randomMode, Localize("RandomMode"));
+ if (randomMode)
+ {
+ float oldValue = randomModeDogfightChance;
+ if (!textInput)
+ {
+ GUI.Label(LeftRect(++line), $"Dogfight ({randomModeDogfightChance:F0}%)");
+ randomModeDogfightChance = GUI.HorizontalSlider(new Rect(leftIndent + contentWidth / 2f, contentTop + (line * entryHeight) + 6, contentWidth / 2f, entryHeight), randomModeDogfightChance, 0f, 100f);
+ }
+ else
+ {
+ GUI.Label(LeftRect(++line), "Dogfight %: ");
+ inputFields["randomModeDogfightChance"].tryParseValue(GUI.TextField(RightRect(line), inputFields["randomModeDogfightChance"].possibleValue, 8, inputFieldStyle));
+ randomModeDogfightChance = inputFields["randomModeDogfightChance"].currentValue;
+ }
+ if (oldValue != randomModeDogfightChance)
+ {
+ var remainder = 100f - randomModeDogfightChance;
+ var total = randomModeIVAChance + randomModeStationaryChance + randomModePathingChance;
+ if (total > 0f)
+ {
+ randomModeIVAChance = Mathf.Round(remainder * randomModeIVAChance / total);
+ randomModeStationaryChance = Mathf.Round(remainder * randomModeStationaryChance / total);
+ randomModePathingChance = Mathf.Round(remainder * randomModePathingChance / total);
+ }
+ else
+ {
+ randomModeIVAChance = Mathf.Round(remainder / 3f);
+ randomModeStationaryChance = Mathf.Round(remainder / 3f);
+ randomModePathingChance = Mathf.Round(remainder / 3f);
+ }
+ randomModeDogfightChance = 100f - randomModeIVAChance - randomModeStationaryChance - randomModePathingChance; // Any rounding errors go to the adjusted slider.
+ inputFields["randomModeDogfightChance"].currentValue = randomModeDogfightChance;
+ inputFields["randomModeIVAChance"].currentValue = randomModeIVAChance;
+ inputFields["randomModeStationaryChance"].currentValue = randomModeStationaryChance;
+ inputFields["randomModePathingChance"].currentValue = randomModePathingChance;
+ }
- if(autoFlybyPosition)
+ oldValue = randomModeIVAChance;
+ if (!textInput)
+ {
+ GUI.Label(LeftRect(++line), $"IVA ({randomModeIVAChance:F0}%)");
+ randomModeIVAChance = GUI.HorizontalSlider(new Rect(leftIndent + contentWidth / 2f, contentTop + (line * entryHeight) + 6f, contentWidth / 2f, entryHeight), randomModeIVAChance, 0f, 100f);
+ }
+ else
+ {
+ GUI.Label(LeftRect(++line), "IVA %: ");
+ inputFields["randomModeIVAChance"].tryParseValue(GUI.TextField(RightRect(line), inputFields["randomModeIVAChance"].possibleValue, 8, inputFieldStyle));
+ randomModeIVAChance = inputFields["randomModeIVAChance"].currentValue;
+ }
+ if (oldValue != randomModeIVAChance)
{
- setPresetOffset = false;
- Vector3 velocity = vessel.srf_velocity;
- if(referenceMode == ReferenceModes.Orbit) velocity = vessel.obt_velocity;
-
- Vector3 clampedVelocity = Mathf.Clamp((float) vessel.srfSpeed, 0, maxRelV) * velocity.normalized;
- float clampedSpeed = clampedVelocity.magnitude;
- float sideDistance = Mathf.Clamp(20 + (clampedSpeed/10), 20, 150);
- float distanceAhead = Mathf.Clamp(4 * clampedSpeed, 30, 3500);
-
- flightCamera.transform.rotation = Quaternion.LookRotation(vessel.transform.position - flightCamera.transform.position, cameraUp);
-
-
- if(referenceMode == ReferenceModes.Surface && vessel.srfSpeed > 0)
- {
- flightCamera.transform.position = vessel.transform.position + (distanceAhead * vessel.srf_velocity.normalized);
- }
- else if(referenceMode == ReferenceModes.Orbit && vessel.obt_speed > 0)
+ var remainder = 100f - randomModeIVAChance;
+ var total = randomModeDogfightChance + randomModeStationaryChance + randomModePathingChance;
+ if (total > 0f)
{
- flightCamera.transform.position = vessel.transform.position + (distanceAhead * vessel.obt_velocity.normalized);
+ randomModeDogfightChance = Mathf.Round(remainder * randomModeDogfightChance / total);
+ randomModeStationaryChance = Mathf.Round(remainder * randomModeStationaryChance / total);
+ randomModePathingChance = Mathf.Round(remainder * randomModePathingChance / total);
}
else
{
- flightCamera.transform.position = vessel.transform.position + (distanceAhead * vessel.vesselTransform.up);
- }
-
-
- if(flightCamera.mode == FlightCamera.Modes.FREE || FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.FREE)
- {
- flightCamera.transform.position += (sideDistance * rightAxis) + (15 * cameraUp);
- }
- else if(flightCamera.mode == FlightCamera.Modes.ORBITAL || FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.ORBITAL)
- {
- flightCamera.transform.position += (sideDistance * FlightGlobals.getUpAxis()) + (15 * Vector3.up);
+ randomModeDogfightChance = Mathf.Round(remainder / 3f);
+ randomModeStationaryChance = Mathf.Round(remainder / 3f);
+ randomModePathingChance = Mathf.Round(remainder / 3f);
}
+ randomModeIVAChance = 100f - randomModeDogfightChance - randomModeStationaryChance - randomModePathingChance; // Any rounding errors go to the adjusted slider.
+ inputFields["randomModeDogfightChance"].currentValue = randomModeDogfightChance;
+ inputFields["randomModeIVAChance"].currentValue = randomModeIVAChance;
+ inputFields["randomModeStationaryChance"].currentValue = randomModeStationaryChance;
+ inputFields["randomModePathingChance"].currentValue = randomModePathingChance;
+ }
-
+ oldValue = randomModeStationaryChance;
+ if (!textInput)
+ {
+ GUI.Label(LeftRect(++line), $"Stationary ({randomModeStationaryChance:F0}%)");
+ randomModeStationaryChance = GUI.HorizontalSlider(new Rect(leftIndent + contentWidth / 2f, contentTop + (line * entryHeight) + 6, contentWidth / 2f, entryHeight), randomModeStationaryChance, 0f, 100f);
}
- else if(manualOffset)
+ else
{
- setPresetOffset = false;
- float sideDistance = manualOffsetRight;
- float distanceAhead = manualOffsetForward;
-
-
- flightCamera.transform.rotation = Quaternion.LookRotation(vessel.transform.position - flightCamera.transform.position, cameraUp);
-
- if(referenceMode == ReferenceModes.Surface && vessel.srfSpeed > 4)
- {
- flightCamera.transform.position = vessel.transform.position + (distanceAhead * vessel.srf_velocity.normalized);
- }
- else if(referenceMode == ReferenceModes.Orbit && vessel.obt_speed > 4)
+ GUI.Label(LeftRect(++line), "Stationary %: ");
+ inputFields["randomModeStationaryChance"].tryParseValue(GUI.TextField(RightRect(line), inputFields["randomModeStationaryChance"].possibleValue, 8, inputFieldStyle));
+ randomModeStationaryChance = inputFields["randomModeStationaryChance"].currentValue;
+ }
+ if (oldValue != randomModeStationaryChance)
+ {
+ var remainder = 100f - randomModeStationaryChance;
+ var total = randomModeDogfightChance + randomModeIVAChance + randomModePathingChance;
+ if (total > 0)
{
- flightCamera.transform.position = vessel.transform.position + (distanceAhead * vessel.obt_velocity.normalized);
+ randomModeDogfightChance = Mathf.Round(remainder * randomModeDogfightChance / total);
+ randomModeIVAChance = Mathf.Round(remainder * randomModeIVAChance / total);
+ randomModePathingChance = Mathf.Round(remainder * randomModePathingChance / total);
}
else
{
- flightCamera.transform.position = vessel.transform.position + (distanceAhead * vessel.vesselTransform.up);
+ randomModeDogfightChance = Mathf.Round(remainder / 3f);
+ randomModeIVAChance = Mathf.Round(remainder / 3f);
+ randomModePathingChance = Mathf.Round(remainder / 3f);
}
-
- if(flightCamera.mode == FlightCamera.Modes.FREE || FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.FREE)
+ randomModeStationaryChance = 100f - randomModeDogfightChance - randomModeIVAChance - randomModePathingChance; // Any rounding errors go to the adjusted slider.
+ inputFields["randomModeDogfightChance"].currentValue = randomModeDogfightChance;
+ inputFields["randomModeIVAChance"].currentValue = randomModeIVAChance;
+ inputFields["randomModeStationaryChance"].currentValue = randomModeStationaryChance;
+ inputFields["randomModePathingChance"].currentValue = randomModePathingChance;
+ }
+
+ oldValue = randomModePathingChance;
+ if (!textInput)
+ {
+ GUI.Label(LeftRect(++line), $"Pathing ({randomModePathingChance:F0}%)");
+ randomModePathingChance = GUI.HorizontalSlider(new Rect(leftIndent + contentWidth / 2f, contentTop + (line * entryHeight) + 6f, contentWidth / 2f, entryHeight), randomModePathingChance, 0f, 100f);
+ }
+ else
+ {
+ GUI.Label(LeftRect(++line), "Pathing %: ");
+ inputFields["randomModePathingChance"].tryParseValue(GUI.TextField(RightRect(line), inputFields["randomModePathingChance"].possibleValue, 8, inputFieldStyle));
+ randomModePathingChance = inputFields["randomModePathingChance"].currentValue;
+ }
+ if (oldValue != randomModePathingChance)
+ {
+ var remainder = 100f - randomModePathingChance;
+ var total = randomModeDogfightChance + randomModeIVAChance + randomModeStationaryChance;
+ if (total > 0)
{
- flightCamera.transform.position += (sideDistance * rightAxis) + (manualOffsetUp * cameraUp);
+ randomModeDogfightChance = Mathf.Round(remainder * randomModeDogfightChance / total);
+ randomModeIVAChance = Mathf.Round(remainder * randomModeIVAChance / total);
+ randomModeStationaryChance = Mathf.Round(remainder * randomModeStationaryChance / total);
}
- else if(flightCamera.mode == FlightCamera.Modes.ORBITAL || FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.ORBITAL)
+ else
{
- flightCamera.transform.position += (sideDistance * FlightGlobals.getUpAxis()) + (manualOffsetUp * Vector3.up);
+ randomModeDogfightChance = Mathf.Round(remainder / 3f);
+ randomModeIVAChance = Mathf.Round(remainder / 3f);
+ randomModeStationaryChance = Mathf.Round(remainder / 3f);
}
+ randomModePathingChance = 100f - randomModeDogfightChance - randomModeIVAChance - randomModeStationaryChance; // Any rounding errors go to the adjusted slider.
+ inputFields["randomModeDogfightChance"].currentValue = randomModeDogfightChance;
+ inputFields["randomModeIVAChance"].currentValue = randomModeIVAChance;
+ inputFields["randomModeStationaryChance"].currentValue = randomModeStationaryChance;
+ inputFields["randomModePathingChance"].currentValue = randomModePathingChance;
}
- else if(setPresetOffset)
- {
- flightCamera.transform.position = presetOffset;
- //setPresetOffset = false;
- }
-
- initialVelocity = vessel.srf_velocity;
- initialOrbit = new Orbit();
- initialOrbit.UpdateFromStateVectors(vessel.orbit.pos, vessel.orbit.vel, FlightGlobals.currentMainBody, Planetarium.GetUniversalTime());
- initialUT = Planetarium.GetUniversalTime();
-
- cameraToolActive = true;
-
- SetDoppler(true);
- AddAtmoAudioControllers(true);
}
- else
+ if (UIScaleFollowsStock)
{
- Debug.Log ("CameraTools: Stationary Camera failed. Active Vessel is null.");
+ if (UIScaleFollowsStock != (UIScaleFollowsStock = GUI.Toggle(ThinRect(++line), UIScaleFollowsStock, Localize("UIScale", $"{LocalizeStr("UIScaleFollowsStock")} ({GameSettings.UI_SCALE:F2}x)"))))
+ { windowRect.x += windowRect.width * (GameSettings.UI_SCALE - UIScale); }
}
- resetPositionFix = flightCamera.transform.position;
- Debug.Log ("flightCamera position post init: "+flightCamera.transform.position);
- }
-
- void RevertCamera()
- {
- posCounter = 0;
-
- if(cameraToolActive)
+ else
{
- presetOffset = flightCamera.transform.position;
- if(camTarget==null)
+ if (UIScaleFollowsStock != (UIScaleFollowsStock = GUI.Toggle(ThinHalfRect(++line), UIScaleFollowsStock, textInput ? Localize("UIScale") : Localize("UIScale", $"{UIScale:F2}x"))))
+ { windowRect.x += windowRect.width * (UIScale - GameSettings.UI_SCALE); }
+ if (!scalingUI) previousUIScale = UIScale;
+ if (!textInput)
{
- savedRotation = flightCamera.transform.rotation;
- hasSavedRotation = true;
+ if (UIScale != (UIScale = MathUtils.RoundToUnit(GUI.HorizontalSlider(new Rect(leftIndent + contentWidth / 2f, contentTop + (line * entryHeight) + 6, contentWidth / 2f, entryHeight), UIScale, 0.5f, 2f), 0.05f)))
+ { scalingUI = true; }
}
else
{
- hasSavedRotation = false;
+ inputFields["UIScale"].tryParseValue(GUI.TextField(RightRect(line), inputFields["UIScale"].possibleValue, 4, inputFieldStyle));
+ if (UIScale != (UIScale = inputFields["UIScale"].currentValue)) windowRect.x += windowRect.width * (previousUIScale - UIScale);
}
}
- hasDied = false;
- if (FlightGlobals.ActiveVessel != null && HighLogic.LoadedScene == GameScenes.FLIGHT)
- {
- flightCamera.SetTarget(FlightGlobals.ActiveVessel.transform, FlightCamera.TargetMode.Vessel);
- }
- flightCamera.transform.parent = origParent;
- flightCamera.transform.position = origPosition;
- flightCamera.transform.rotation = origRotation;
- Camera.main.nearClipPlane = origNearClip;
-
- flightCamera.SetFoV(60);
- flightCamera.ActivateUpdate();
- currentFOV = 60;
-
- cameraToolActive = false;
-
- ResetDoppler();
- if(OnResetCTools != null)
- {
- OnResetCTools();
- }
-
- StopPlayingPath();
- }
-
- void SaveOriginalCamera()
- {
- origPosition = flightCamera.transform.position;
- origRotation = flightCamera.transform.localRotation;
- origParent = flightCamera.transform.parent;
- origNearClip = Camera.main.nearClipPlane;
- }
-
- Part GetPartFromMouse()
- {
- Vector3 mouseAim = new Vector3(Input.mousePosition.x/Screen.width, Input.mousePosition.y/Screen.height, 0);
- Ray ray = FlightCamera.fetch.mainCamera.ViewportPointToRay(mouseAim);
- RaycastHit hit;
- if(Physics.Raycast(ray, out hit, 10000, 1<<0))
- {
- Part p = hit.transform.GetComponentInParent();
- return p;
- }
- else return null;
- }
-
- Vector3 GetPosFromMouse()
- {
- Vector3 mouseAim = new Vector3(Input.mousePosition.x/Screen.width, Input.mousePosition.y/Screen.height, 0);
- Ray ray = FlightCamera.fetch.mainCamera.ViewportPointToRay(mouseAim);
- RaycastHit hit;
- if(Physics.Raycast(ray, out hit, 15000, 557057))
- {
- return hit.point - (10 * ray.direction);
- }
- else return Vector3.zero;
- }
-
- void PostDeathRevert()
- {
- if(cameraToolActive)
- {
- RevertCamera();
- }
- }
- void PostDeathRevert(GameScenes f)
- {
- if(cameraToolActive)
- {
- RevertCamera();
- }
- }
-
- void PostDeathRevert(Vessel v)
- {
- if(cameraToolActive)
+ ++line;
+ if (toolMode != ToolModes.Pathing)
{
- RevertCamera();
+ autoFOV = GUI.Toggle(LabelRect(++line), autoFOV, Localize("Autozoom"));
}
- }
-
- //GUI
- void OnGUI()
- {
- if(guiEnabled && gameUIToggle)
+ if (autoFOV && toolMode != ToolModes.Pathing)
{
- windowRect = GUI.Window(320, windowRect, GuiWindow, "");
-
- if(showKeyframeEditor)
+ GUI.Label(LeftRect(++line), Localize("AutozoomMargin"));
+ if (!textInput)
{
- KeyframeEditorWindow();
+ autoZoomMargin = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + ((++line) * entryHeight), contentWidth - 45, entryHeight), autoZoomMargin, 0, autoZoomMarginMax);
+ if (!enableKeypad) autoZoomMargin = Mathf.RoundToInt(autoZoomMargin * 2f) / 2f;
+ GUI.Label(new Rect(leftIndent + contentWidth - 40, contentTop + ((line - 0.15f) * entryHeight), 40, entryHeight), autoZoomMargin.ToString("G4"), leftLabel);
}
- if(showPathSelectorWindow)
+ else
{
- PathSelectorWindow();
+ inputFields["autoZoomMargin"].tryParseValue(GUI.TextField(RightRect(line), inputFields["autoZoomMargin"].possibleValue, 8, inputFieldStyle));
+ autoZoomMargin = inputFields["autoZoomMargin"].currentValue;
}
}
- }
-
- void GuiWindow(int windowID)
- {
- GUI.DragWindow(new Rect(0,0,windowWidth, draggableHeight));
-
- GUIStyle centerLabel = new GUIStyle();
- centerLabel.alignment = TextAnchor.UpperCenter;
- centerLabel.normal.textColor = Color.white;
-
- GUIStyle leftLabel = new GUIStyle();
- leftLabel.alignment = TextAnchor.UpperLeft;
- leftLabel.normal.textColor = Color.white;
-
- GUIStyle leftLabelBold = new GUIStyle(leftLabel);
- leftLabelBold.fontStyle = FontStyle.Bold;
-
-
-
- float line = 1;
- float contentWidth = (windowWidth) - (2*leftIndent);
- float contentTop = 20;
- GUIStyle titleStyle = new GUIStyle(centerLabel);
- titleStyle.fontSize = 24;
- titleStyle.alignment = TextAnchor.MiddleCenter;
- GUI.Label(new Rect(0, contentTop, windowWidth, 40), "Camera Tools", titleStyle);
- line++;
- float parseResult;
-
- //tool mode switcher
- GUI.Label(new Rect(leftIndent, contentTop+(line*entryHeight), contentWidth, entryHeight), "Tool: "+toolMode.ToString(), leftLabelBold);
- line++;
- if(!cameraToolActive)
+ else
{
- if(GUI.Button(new Rect(leftIndent, contentTop + (line * entryHeight), 25, entryHeight - 2), "<"))
+ GUI.Label(LeftRect(++line), "Zoom:", leftLabel);
+ if (!textInput)
{
- CycleToolMode(false);
+ zoomExp = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + ((++line) * entryHeight), contentWidth - 45, entryHeight), zoomExp, 1, zoomMaxExp);
+ GUI.Label(new Rect(leftIndent + contentWidth - 40, contentTop + ((line - 0.15f) * entryHeight), 40, entryHeight), zoomFactor.ToString("G5") + "x", leftLabel);
}
- if(GUI.Button(new Rect(leftIndent + 25 + 4, contentTop + (line * entryHeight), 25, entryHeight - 2), ">"))
+ else
{
- CycleToolMode(true);
+ inputFields["zoomFactor"].tryParseValue(GUI.TextField(RightRect(line), inputFields["zoomFactor"].possibleValue, 8, inputFieldStyle));
+ zoomExp = Mathf.Log(inputFields["zoomFactor"].currentValue) + 1f;
}
}
- line++;
- line++;
- if(autoFOV)
+
+ GUI.Label(LeftRect(++line), Localize("CameraShake"));
+ if (!textInput)
{
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth / 2, entryHeight), "Autozoom Margin: ");
- line++;
- autoZoomMargin = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + ((line) * entryHeight), contentWidth - 45, entryHeight), autoZoomMargin, 0, 50);
- GUI.Label(new Rect(leftIndent + contentWidth - 40, contentTop + ((line - 0.15f) * entryHeight), 40, entryHeight), autoZoomMargin.ToString("0.0"), leftLabel);
+ shakeMultiplier = Mathf.Round(GUI.HorizontalSlider(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth - 45, entryHeight), shakeMultiplier, 0f, 10f) * 10f) / 10f;
+ GUI.Label(new Rect(leftIndent + contentWidth - 40, contentTop + ((line - 0.25f) * entryHeight), 40, entryHeight), shakeMultiplier.ToString("G3") + "x");
}
else
{
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Zoom:", leftLabel);
- line++;
- zoomExp = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + ((line) * entryHeight), contentWidth - 45, entryHeight), zoomExp, 1, 8);
- GUI.Label(new Rect(leftIndent + contentWidth - 40, contentTop + ((line - 0.15f) * entryHeight), 40, entryHeight), zoomFactor.ToString("0.0") + "x", leftLabel);
- }
- line++;
-
- if(toolMode != ToolModes.Pathing)
- {
- autoFOV = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), autoFOV, "Auto Zoom");//, leftLabel);
- line++;
+ inputFields["shakeMultiplier"].tryParseValue(GUI.TextField(RightRect(line), inputFields["shakeMultiplier"].possibleValue, 8, inputFieldStyle));
+ shakeMultiplier = inputFields["shakeMultiplier"].currentValue;
}
- line++;
- useAudioEffects = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), useAudioEffects, "Use Audio Effects");
- line++;
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Camera shake:");
- line++;
- shakeMultiplier = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth - 45, entryHeight), shakeMultiplier, 0f, 10f);
- GUI.Label(new Rect(leftIndent + contentWidth - 40, contentTop + ((line - 0.25f) * entryHeight), 40, entryHeight), shakeMultiplier.ToString("0.00") + "x");
- line++;
- line++;
+ ++line;
//Stationary camera GUI
- if(toolMode == ToolModes.StationaryCamera)
+ if (toolMode == ToolModes.StationaryCamera)
{
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Frame of Reference: " + referenceMode.ToString(), leftLabel);
- line++;
- if(GUI.Button(new Rect(leftIndent, contentTop + (line * entryHeight), 25, entryHeight - 2), "<"))
- {
- CycleReferenceMode(false);
- }
- if(GUI.Button(new Rect(leftIndent + 25 + 4, contentTop + (line * entryHeight), 25, entryHeight - 2), ">"))
- {
- CycleReferenceMode(true);
- }
-
- line++;
-
- if(referenceMode == ReferenceModes.Surface || referenceMode == ReferenceModes.Orbit)
- {
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth / 2, entryHeight), "Max Rel. V: ", leftLabel);
- maxRelV = float.Parse(GUI.TextField(new Rect(leftIndent + contentWidth / 2, contentTop + (line * entryHeight), contentWidth / 2, entryHeight), maxRelV.ToString()));
- }
- else if(referenceMode == ReferenceModes.InitialVelocity)
- {
- useOrbital = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), useOrbital, " Orbital");
- }
- line++;
-
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Camera Position:", leftLabel);
- line++;
- string posButtonText = "Set Position w/ Click";
- if(setPresetOffset) posButtonText = "Clear Position";
- if(waitingForPosition) posButtonText = "Waiting...";
- if(FlightGlobals.ActiveVessel != null && GUI.Button(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight - 2), posButtonText))
+ GUI.Label(LeftRect(++line), Localize("MaxRelVel"), leftLabel);
+ inputFields["maxRelV"].tryParseValue(GUI.TextField(RightRect(line), inputFields["maxRelV"].possibleValue, 12, inputFieldStyle));
+ maxRelV = inputFields["maxRelV"].currentValue;
+ signedMaxRelVSqr = Mathf.Abs(maxRelV) * maxRelV;
+
+ maintainInitialVelocity = GUI.Toggle(LeftRect(++line), maintainInitialVelocity, Localize("MaintainVel"));
+ if (maintainInitialVelocity) useOrbital = GUI.Toggle(RightRect(line), useOrbital, Localize("UseOrbital"));
+
+ // GUI.Label(LeftRect(++line), $"time offset: {δt}", leftLabel);
+ // δt = Mathf.Round(GUI.HorizontalSlider(RightRect(line), δt, -2f, 2f) * 4f) / 4f;
+
+ GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), Localize("CameraPosition"), leftLabel);
+ var posButtonText = Localize("SetPositionClick");
+ if (setPresetOffset) posButtonText = Localize("ClearPosition");
+ if (waitingForPosition) posButtonText = Localize("Waiting");
+ if (FlightGlobals.ActiveVessel != null && GUI.Button(ThinRect(++line), posButtonText))
{
- if(setPresetOffset)
+ if (setPresetOffset)
{
setPresetOffset = false;
}
@@ -1353,108 +3238,84 @@ void GuiWindow(int windowID)
mouseUp = false;
}
}
- line++;
-
-
- autoFlybyPosition = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), autoFlybyPosition, "Auto Flyby Position");
- if(autoFlybyPosition) manualOffset = false;
- line++;
-
- manualOffset = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), manualOffset, "Manual Flyby Position");
- line++;
-
+ autoFlybyPosition = GUI.Toggle(LabelRect(++line), autoFlybyPosition, Localize("AutoFlybyPos"));
+ autoLandingPosition = GUI.Toggle(LabelRect(++line), autoLandingPosition, Localize("AutoLandingPos")); ;
+ if (autoFlybyPosition || autoLandingPosition) { manualOffset = false; }
+ manualOffset = GUI.Toggle(LabelRect(++line), manualOffset, Localize("ManualFlybyPos"));
Color origGuiColor = GUI.color;
- if(manualOffset)
- {
- autoFlybyPosition = false;
- }
- else
- {
- GUI.color = new Color(0.5f, 0.5f, 0.5f, origGuiColor.a);
- }
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), 60, entryHeight), "Fwd:", leftLabel);
+ if (manualOffset)
+ { autoFlybyPosition = false; autoLandingPosition = false; }
+ else if (!autoLandingPosition)
+ { GUI.color = new Color(0.5f, 0.5f, 0.5f, origGuiColor.a); }
+
+ GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), 60, entryHeight), Localize("FwdPos"), leftLabel);
float textFieldWidth = 42;
Rect fwdFieldRect = new Rect(leftIndent + contentWidth - textFieldWidth - (3 * incrButtonWidth), contentTop + (line * entryHeight), textFieldWidth, entryHeight);
guiOffsetForward = GUI.TextField(fwdFieldRect, guiOffsetForward.ToString());
- if(float.TryParse(guiOffsetForward, out parseResult))
+ if (float.TryParse(guiOffsetForward, out parseResult))
{
- manualOffsetForward = parseResult;
+ manualOffsetForward = parseResult;
}
DrawIncrementButtons(fwdFieldRect, ref manualOffsetForward);
guiOffsetForward = manualOffsetForward.ToString();
- line++;
+ GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), 60, entryHeight), Localize("RightPos"), leftLabel);
Rect rightFieldRect = new Rect(fwdFieldRect.x, contentTop + (line * entryHeight), textFieldWidth, entryHeight);
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), 60, entryHeight), "Right:", leftLabel);
guiOffsetRight = GUI.TextField(rightFieldRect, guiOffsetRight);
- if(float.TryParse(guiOffsetRight, out parseResult))
+ if (float.TryParse(guiOffsetRight, out parseResult))
{
- manualOffsetRight = parseResult;
+ manualOffsetRight = parseResult;
}
DrawIncrementButtons(rightFieldRect, ref manualOffsetRight);
guiOffsetRight = manualOffsetRight.ToString();
- line++;
+ GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), 60, entryHeight), Localize("UpPos"), leftLabel);
Rect upFieldRect = new Rect(fwdFieldRect.x, contentTop + (line * entryHeight), textFieldWidth, entryHeight);
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), 60, entryHeight), "Up:", leftLabel);
guiOffsetUp = GUI.TextField(upFieldRect, guiOffsetUp);
- if(float.TryParse(guiOffsetUp, out parseResult))
- {
- manualOffsetUp = parseResult;
- }
+ if (float.TryParse(guiOffsetUp, out parseResult))
+ { manualOffsetUp = parseResult; }
DrawIncrementButtons(upFieldRect, ref manualOffsetUp);
guiOffsetUp = manualOffsetUp.ToString();
GUI.color = origGuiColor;
+ ++line;
- line++;
- line++;
-
- string targetText = "None";
- if(camTarget != null) targetText = camTarget.gameObject.name;
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Camera Target: " + targetText, leftLabel);
- line++;
- string tgtButtonText = "Set Target w/ Click";
- if(waitingForTarget) tgtButtonText = "waiting...";
- if(GUI.Button(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight - 2), tgtButtonText))
+ GUI.Label(LabelRect(++line), Localize("CameraTarget", camTarget == null ? "None" : camTarget.partInfo.title), leftLabel);
+ if (GUI.Button(ThinRect(++line), waitingForTarget ? Localize("Waiting") : Localize("SetTargetClick")))
{
waitingForTarget = true;
mouseUp = false;
}
- line++;
- if(GUI.Button(new Rect(leftIndent, contentTop + (line * entryHeight), (contentWidth / 2) - 2, entryHeight - 2), "Target Self"))
+ if (GUI.Button(ThinHalfRect(++line, 0), Localize("TargetSelf")))
{
camTarget = FlightGlobals.ActiveVessel.GetReferenceTransformPart();
hasTarget = true;
}
- if(GUI.Button(new Rect(2 + leftIndent + contentWidth / 2, contentTop + (line * entryHeight), (contentWidth / 2) - 2, entryHeight - 2), "Clear Target"))
+ if (GUI.Button(ThinHalfRect(line, 1), Localize("ClearTarget")))
{
camTarget = null;
hasTarget = false;
}
- line++;
-
- targetCoM = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight - 2), targetCoM, "Vessel Center of Mass");
+ if (!(saveRotation = GUI.Toggle(ThinHalfRect(++line, 0), saveRotation, Localize("SaveRotation")))) { hasSavedRotation = false; }
+ if (GUI.Button(ThinHalfRect(line, 1), Localize("ResetRoll"))) ResetRoll();
+ targetCoM = GUI.Toggle(ThinRect(++line), targetCoM, Localize("VesselCoM"));
}
- else if(toolMode == ToolModes.DogfightCamera)
+ else if (toolMode == ToolModes.DogfightCamera)
{
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Secondary target:");
- line++;
+ GUI.Label(LabelRect(++line), Localize("SecondaryTarget"));
string tVesselLabel;
- if(showingVesselList)
- {
- tVesselLabel = "Clear";
- }
- else if(dogfightTarget)
- {
- tVesselLabel = dogfightTarget.vesselName;
- }
+ if (MouseAimFlight.IsMouseAimActive)
+ { tVesselLabel = "MouseAimFlight"; }
+ else if (showingVesselList)
+ { tVesselLabel = LocalizeStr("Clear"); }
+ else if (BDArmory.hasBDA && bdArmory.useCentroid)
+ { tVesselLabel = LocalizeStr("Centroid"); }
+ else if (dogfightTarget)
+ { tVesselLabel = dogfightTarget.vesselName; }
else
+ { tVesselLabel = LocalizeStr("None"); }
+ if (GUI.Button(LabelRect(++line), tVesselLabel))
{
- tVesselLabel = "None";
- }
- if(GUI.Button(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), tVesselLabel))
- {
- if(showingVesselList)
+ if (showingVesselList)
{
showingVesselList = false;
dogfightTarget = null;
@@ -1465,95 +3326,230 @@ void GuiWindow(int windowID)
showingVesselList = true;
}
}
- line++;
-
- if(showingVesselList)
+ if (showingVesselList)
{
- foreach(var v in loadedVessels)
+ if (MouseAimFlight.IsMouseAimActive) showingVesselList = false;
+ foreach (var v in loadedVessels)
{
- if(!v || !v.loaded) continue;
- if(GUI.Button(new Rect(leftIndent + 10, contentTop + (line * entryHeight), contentWidth - 10, entryHeight), v.vesselName))
+ if (!v || !v.loaded) continue;
+ if (GUI.Button(new Rect(leftIndent + 10f, contentTop + (++line * entryHeight), contentWidth - 10f, entryHeight), v.vesselName))
{
dogfightTarget = v;
showingVesselList = false;
}
- line++;
}
}
- line++;
+ if (BDArmory.hasBDA)
+ {
+ if (bdArmory.hasBDAI)
+ {
+ if (bdArmory.useBDAutoTarget != (bdArmory.useBDAutoTarget = GUI.Toggle(ThinRect(++line), bdArmory.useBDAutoTarget, Localize("BDAAutoTarget"))) && bdArmory.useBDAutoTarget)
+ { bdArmory.useCentroid = false; }
+ GUI.Label(SliderLabelLeft(++line, 120f), Localize("MinimumInterval"));
+ if (!textInput)
+ {
+ bdArmory.AItargetMinimumUpdateInterval = MathUtils.RoundToUnit(GUI.HorizontalSlider(SliderRect(line, 120f), bdArmory.AItargetMinimumUpdateInterval, 0.5f, 5f), 0.5f);
+ GUI.Label(SliderLabelRight(line), $"{bdArmory.AItargetMinimumUpdateInterval:F1}s");
+ }
+ else
+ {
+ bdArmory.inputFields["AItargetMinimumUpdateInterval"].tryParseValue(GUI.TextField(RightRect(line), bdArmory.inputFields["AItargetMinimumUpdateInterval"].possibleValue, 8, inputFieldStyle));
+ bdArmory.AItargetMinimumUpdateInterval = bdArmory.inputFields["AItargetMinimumUpdateInterval"].currentValue;
+ }
+ GUI.Label(SliderLabelLeft(++line, 120f), Localize("SecondaryTargetDeathSwitchDelay"));
+ if (!textInput)
+ {
+ bdArmory.AItargetSecondaryTargetDeathSwitchDelay = MathUtils.RoundToUnit(GUI.HorizontalSlider(SliderRect(line, 120f), bdArmory.AItargetSecondaryTargetDeathSwitchDelay, 0f, 5f), 0.5f);
+ GUI.Label(SliderLabelRight(line), $"{bdArmory.AItargetSecondaryTargetDeathSwitchDelay:F1}s");
+ }
+ else
+ {
+ bdArmory.inputFields["AItargetSecondaryTargetDeathSwitchDelay"].tryParseValue(GUI.TextField(RightRect(line), bdArmory.inputFields["AItargetSecondaryTargetDeathSwitchDelay"].possibleValue, 8, inputFieldStyle));
+ bdArmory.AItargetSecondaryTargetDeathSwitchDelay = bdArmory.inputFields["AItargetSecondaryTargetDeathSwitchDelay"].currentValue;
+ }
+ bdArmory.autoTargetIncomingMissiles = GUI.Toggle(ThinRect(++line), bdArmory.autoTargetIncomingMissiles, Localize("TargetIncomingMissiles"));
+ if (bdArmory.autoTargetIncomingMissiles)
+ {
+ GUI.Label(SliderLabelLeft(++line, 120f), Localize("MinimumIntervalMissiles"));
+ if (!textInput)
+ {
+ bdArmory.AItargetMinimumMissileUpdateInterval = MathUtils.RoundToUnit(GUI.HorizontalSlider(SliderRect(line, 120f), bdArmory.AItargetMinimumMissileUpdateInterval, 0f, 1f), 0.1f);
+ GUI.Label(SliderLabelRight(line), $"{bdArmory.AItargetMinimumMissileUpdateInterval:F1}s");
+ }
+ else
+ {
+ bdArmory.inputFields["AItargetMinimumMissileUpdateInterval"].tryParseValue(GUI.TextField(RightRect(line), bdArmory.inputFields["AItargetMinimumMissileUpdateInterval"].possibleValue, 8, inputFieldStyle));
+ bdArmory.AItargetMinimumMissileUpdateInterval = bdArmory.inputFields["AItargetMinimumMissileUpdateInterval"].currentValue;
+ }
+ }
+ }
+ if (bdArmory.useCentroid != (bdArmory.useCentroid = GUI.Toggle(ThinRect(++line), bdArmory.useCentroid, Localize("TargetDogfightCentroid"))) && bdArmory.useCentroid)
+ { bdArmory.useBDAutoTarget = false; }
+ }
+
+ ++line;
+
+ GUI.Label(SliderLabelLeft(++line, 60f), Localize("Distance"));
+ if (!textInput)
+ {
+ float widthAdjust = enableKeypad ? 15f : 5f;
+ dogfightDistance = GUI.HorizontalSlider(SliderRect(++line, 0f, -widthAdjust), dogfightDistance, 1f, dogfightMaxDistance);
+ if (!enableKeypad) dogfightDistance = MathUtils.RoundToUnit(dogfightDistance, 1f);
+ GUI.Label(SliderLabelRight(line, widthAdjust), $"{dogfightDistance:G4}m");
+ }
+ else
+ {
+ inputFields["dogfightDistance"].tryParseValue(GUI.TextField(RightRect(line), inputFields["dogfightDistance"].possibleValue, 8, inputFieldStyle));
+ dogfightDistance = inputFields["dogfightDistance"].currentValue;
+ }
- if(hasBDAI)
+ GUI.Label(LeftRect(++line), Localize("Offset"));
+ if (!textInput)
+ {
+ float widthAdjust = enableKeypad ? 10f : 0f;
+ GUI.Label(SliderLabelLeft(++line, 15f), Localize("X"));
+ dogfightOffsetX = GUI.HorizontalSlider(SliderRect(line, 15f, -widthAdjust), dogfightOffsetX, -dogfightMaxOffset, dogfightMaxOffset);
+ if (!enableKeypad) dogfightOffsetX = MathUtils.RoundToUnit(dogfightOffsetX, 1f);
+ GUI.Label(SliderLabelRight(line, widthAdjust), $"{dogfightOffsetX:G3}m");
+ GUI.Label(SliderLabelLeft(++line, 15f), Localize("Y"));
+ dogfightOffsetY = GUI.HorizontalSlider(SliderRect(line, 15f, -widthAdjust), dogfightOffsetY, -dogfightMaxOffset, dogfightMaxOffset);
+ if (!enableKeypad) dogfightOffsetY = MathUtils.RoundToUnit(dogfightOffsetY, 1f);
+ GUI.Label(SliderLabelRight(line, widthAdjust), $"{dogfightOffsetY:G3}m");
+ line += 0.5f;
+
+ GUI.Label(SliderLabelLeft(++line, 30f), Localize("Lerp"));
+ dogfightLerp = Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, 30f), dogfightLerp * 100f, 1f, 50f)) / 100f;
+ GUI.Label(SliderLabelRight(line), $"{dogfightLerp:G3}");
+ GUI.Label(SliderLabelLeft(++line, 30f), Localize("Roll"));
+ dogfightRoll = Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, 30f), dogfightRoll * 20f, 0f, 20f)) / 20f;
+ GUI.Label(SliderLabelRight(line), $"{dogfightRoll:G3}");
+ line += 0.15f;
+ }
+ else
{
- useBDAutoTarget = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight - 2), useBDAutoTarget, "BDA AI Auto target");
- line++;
+ GUI.Label(QuarterRect(++line, 0), Localize("X"), rightLabel);
+ inputFields["dogfightOffsetX"].tryParseValue(GUI.TextField(QuarterRect(line, 1), inputFields["dogfightOffsetX"].possibleValue, 8, inputFieldStyle));
+ dogfightOffsetX = inputFields["dogfightOffsetX"].currentValue;
+ GUI.Label(QuarterRect(line, 2), Localize("Y"), rightLabel);
+ inputFields["dogfightOffsetY"].tryParseValue(GUI.TextField(QuarterRect(line, 3), inputFields["dogfightOffsetY"].possibleValue, 8, inputFieldStyle));
+ dogfightOffsetY = inputFields["dogfightOffsetY"].currentValue;
+ GUI.Label(QuarterRect(++line, 0), Localize("Lerp"), rightLabel);
+ inputFields["dogfightLerp"].tryParseValue(GUI.TextField(QuarterRect(line, 1), inputFields["dogfightLerp"].possibleValue, 8, inputFieldStyle));
+ dogfightLerp = inputFields["dogfightLerp"].currentValue;
+ GUI.Label(QuarterRect(line, 2), Localize("Roll"), rightLabel);
+ inputFields["dogfightRoll"].tryParseValue(GUI.TextField(QuarterRect(line, 3), inputFields["dogfightRoll"].possibleValue, 8, inputFieldStyle));
+ dogfightRoll = inputFields["dogfightRoll"].currentValue;
}
- line++;
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth / 2, entryHeight), "Distance: " + dogfightDistance.ToString("0.0"));
- line++;
- dogfightDistance = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), dogfightDistance, 1, 100);
- line += 1.5f;
+ GUI.Label(SliderLabelLeft(++line, 95f), Localize("FreeLookThr"));
+ if (!textInput)
+ {
+ freeLookThresholdSqr = MathUtils.RoundToUnit(GUI.HorizontalSlider(SliderRect(line, 95f), freeLookThresholdSqr, 0f, 1f), 0.1f);
+ GUI.Label(SliderLabelRight(line), $"{freeLookThresholdSqr:G2}");
+ }
+ else
+ {
+ inputFields["freeLookThresholdSqr"].tryParseValue(GUI.TextField(RightRect(line), inputFields["freeLookThresholdSqr"].possibleValue, 8, inputFieldStyle));
+ freeLookThresholdSqr = inputFields["freeLookThresholdSqr"].currentValue;
+ }
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Offset:");
- line++;
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), 15, entryHeight), "X: ");
- dogfightOffsetX = GUI.HorizontalSlider(new Rect(leftIndent + 15, contentTop + (line * entryHeight) + 6, contentWidth - 45, entryHeight), dogfightOffsetX, -dogfightMaxOffset, dogfightMaxOffset);
- GUI.Label(new Rect(leftIndent + contentWidth - 25, contentTop + (line * entryHeight), 25, entryHeight), dogfightOffsetX.ToString("0.0"));
- line++;
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), 15, entryHeight), "Y: ");
- dogfightOffsetY = GUI.HorizontalSlider(new Rect(leftIndent + 15, contentTop + (line * entryHeight) + 6, contentWidth - 45, entryHeight), dogfightOffsetY, -dogfightMaxOffset, dogfightMaxOffset);
- GUI.Label(new Rect(leftIndent + contentWidth - 25, contentTop + (line * entryHeight), 25, entryHeight), dogfightOffsetY.ToString("0.0"));
- line += 1.5f;
- }
- else if(toolMode == ToolModes.Pathing)
- {
- if(selectedPathIndex >= 0)
+ GUI.Label(SliderLabelLeft(++line, 95f), Localize("CameraInertia"));
+ if (!textInput)
{
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Path:");
- currentPath.pathName = GUI.TextField(new Rect(leftIndent + 34, contentTop + (line * entryHeight), contentWidth-34, entryHeight), currentPath.pathName);
+ dogfightInertialFactor = MathUtils.RoundToUnit(GUI.HorizontalSlider(SliderRect(line, 95f), dogfightInertialFactor, 0f, 1f), 0.1f);
+ GUI.Label(SliderLabelRight(line), $"{dogfightInertialFactor:G2}");
}
else
{
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Path: None");
+ inputFields["dogfightInertialFactor"].tryParseValue(GUI.TextField(RightRect(line), inputFields["dogfightInertialFactor"].possibleValue, 8, inputFieldStyle));
+ dogfightInertialFactor = inputFields["dogfightInertialFactor"].currentValue;
}
- line += 1.25f;
- if(GUI.Button(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Open Path"))
+
+ if (dogfightInertialChaseMode != (dogfightInertialChaseMode = GUI.Toggle(LabelRect(++line), dogfightInertialChaseMode, Localize("InertialChaseMode"))))
{
- TogglePathList();
+ dogfightLerpMomentum = default;
+ dogfightLerpDelta = default;
+ dogfightRotationTarget = vessel != null ? vessel.CoM : default;
}
- line++;
- if(GUI.Button(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth/2, entryHeight), "New Path"))
+
+ GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), Localize("DogfightOffsetMode"));
+ dogfightOffsetMode = (DogfightOffsetMode)Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, contentWidth / 2f - 25f, -25f), (int)dogfightOffsetMode, 0, DogfightOffsetModeMax));
+ GUI.Label(SliderLabelRight(line, 25f), dogfightOffsetMode.ToString());
+
+ if (dogfightChasePlaneMode != (dogfightChasePlaneMode = GUI.Toggle(LabelRect(++line), dogfightChasePlaneMode, Localize("ChasePlaneMode"))))
{
- CreateNewPath();
+ if (!dogfightChasePlaneMode) camTarget = null;
+ }
+ if (dogfightChasePlaneMode)
+ { // Co-op the stationary camera's target choosing for targeting specific parts.
+ if (GUI.Button(ThinRect(++line), waitingForTarget ? Localize("Waiting") : Localize("ChasePlaneTarget", camTarget == null ? "CoM" : camTarget.partInfo.title)))
+ {
+ waitingForTarget = true;
+ mouseUp = false;
+ }
}
- if(GUI.Button(new Rect(leftIndent + (contentWidth/2), contentTop + (line * entryHeight), contentWidth / 2, entryHeight), "Delete Path"))
+ }
+ else if (toolMode == ToolModes.Pathing)
+ {
+ if (selectedPathIndex >= 0)
{
- DeletePath(selectedPathIndex);
+ GUI.Label(LabelRect(++line), Localize("Path"));
+ currentPath.pathName = GUI.TextField(new Rect(leftIndent + 34, contentTop + (line * entryHeight), contentWidth - 34, entryHeight), currentPath.pathName);
}
- line ++;
- if(selectedPathIndex >= 0)
+ else
+ { GUI.Label(LabelRect(++line), Localize("NoPath")); }
+ line += 0.25f;
+ if (GUI.Button(LabelRect(++line), Localize("OpenPath")))
+ { TogglePathList(); }
+ if (GUI.Button(HalfRect(++line, 0), Localize("NewPath")))
+ { CreateNewPath(); }
+ if (GUI.Button(HalfRect(line, 1), Localize("DeletePath")))
+ { DeletePath(selectedPathIndex); }
+ line += 0.25f;
+
+ if (selectedPathIndex >= 0)
{
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Interpolation rate: " + currentPath.lerpRate.ToString("0.0"));
- line++;
- currentPath.lerpRate = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + (line * entryHeight) + 4, contentWidth - 50, entryHeight), currentPath.lerpRate, 1f, 15f);
- currentPath.lerpRate = Mathf.Round(currentPath.lerpRate * 10) / 10;
- line++;
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Path timescale " + currentPath.timeScale.ToString("0.00"));
- line++;
- currentPath.timeScale = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + (line * entryHeight) + 4, contentWidth - 50, entryHeight), currentPath.timeScale, 0.05f, 4f);
- currentPath.timeScale = Mathf.Round(currentPath.timeScale * 20) / 20;
- line++;
- float viewHeight = Mathf.Max(6 * entryHeight, currentPath.keyframeCount * entryHeight);
- Rect scrollRect = new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, 6 * entryHeight);
+ if (!textInput)
+ {
+ GUI.Label(LabelRect(++line), Localize("SecondarySmoothing", currentPath.secondarySmoothing.ToString("G2")));
+ if (currentPath.secondarySmoothing != (currentPath.secondarySmoothing = Mathf.Round(GUI.HorizontalSlider(new Rect(leftIndent, contentTop + (++line * entryHeight) + 4f, contentWidth, entryHeight), currentPath.secondarySmoothing, 0f, 1f) * 100f) / 100f))
+ { pathingLerpRate = Mathf.Pow(10, -2f * currentPath.secondarySmoothing); }
+ }
+ else
+ {
+ GUI.Label(LeftRect(++line), Localize("SecondarySmoothing"));
+ inputFields["pathingSecondarySmoothing"].tryParseValue(GUI.TextField(RightRect(line), inputFields["pathingSecondarySmoothing"].possibleValue, 8, inputFieldStyle));
+ if (currentPath.secondarySmoothing != (currentPath.secondarySmoothing = inputFields["pathingSecondarySmoothing"].currentValue))
+ { pathingLerpRate = Mathf.Pow(10, -2f * currentPath.secondarySmoothing); }
+ }
+ if (!textInput)
+ {
+ GUI.Label(LabelRect(++line), Localize("PathTimescale", currentPath.timeScale.ToString("G3")));
+ currentPath.timeScale = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + (++line * entryHeight) + 4f, contentWidth, entryHeight), currentPath.timeScale, 0.05f, 4f);
+ currentPath.timeScale = Mathf.Round(currentPath.timeScale * 20f) / 20f;
+ }
+ else
+ {
+ GUI.Label(LeftRect(++line), Localize("PathTimescale"));
+ inputFields["pathingTimeScale"].tryParseValue(GUI.TextField(RightRect(line), inputFields["pathingTimeScale"].possibleValue, 8, inputFieldStyle));
+ currentPath.timeScale = inputFields["pathingTimeScale"].currentValue;
+ }
+ if (GUI.Button(HalfRect(++line, 0), useRealTime ? Localize("RealTime") : Localize("InGameTime")))
+ { useRealTime = !useRealTime; }
+ if (GUI.Button(HalfRect(line, 1), currentPath.isGeoSpatial ? Localize("GeoSpatialPath") : Localize("StandardPath")))
+ { currentPath.isGeoSpatial = !currentPath.isGeoSpatial; }
+ line += 0.5f;
+ float viewHeight = Mathf.Max(6f * entryHeight, currentPath.keyframeCount * entryHeight);
+ Rect scrollRect = new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, 6 * entryHeight);
GUI.Box(scrollRect, string.Empty);
- float viewContentWidth = contentWidth - (2 * leftIndent);
- keysScrollPos = GUI.BeginScrollView(scrollRect, keysScrollPos, new Rect(0, 0, viewContentWidth, viewHeight));
- if(currentPath.keyframeCount > 0)
+ float viewContentWidth = contentWidth - (2f * leftIndent);
+ keysScrollPos = GUI.BeginScrollView(scrollRect, keysScrollPos, new Rect(0f, 0f, viewContentWidth, viewHeight));
+ if (currentPath.keyframeCount > 0)
{
Color origGuiColor = GUI.color;
- for(int i = 0; i < currentPath.keyframeCount; i++)
+ for (int i = 0; i < currentPath.keyframeCount; ++i)
{
- if(i == currentKeyframeIndex)
+ if (i == currentKeyframeIndex)
{
GUI.color = Color.green;
}
@@ -1561,208 +3557,208 @@ void GuiWindow(int windowID)
{
GUI.color = origGuiColor;
}
- string kLabel = "#" + i.ToString() + ": " + currentPath.GetKeyframe(i).time.ToString("0.00") + "s";
- if(GUI.Button(new Rect(0, (i * entryHeight), 3 * viewContentWidth / 4, entryHeight), kLabel))
+ string kLabel = "#" + i.ToString() + ": " + currentPath.GetKeyframe(i).time.ToString("G3") + "s";
+ if (GUI.Button(new Rect(0f, i * entryHeight, 3f * viewContentWidth / 4f, entryHeight), kLabel))
{
SelectKeyframe(i);
}
- if(GUI.Button(new Rect((3 * contentWidth / 4), (i * entryHeight), (viewContentWidth / 4) - 20, entryHeight), "X"))
+ if (GUI.Button(new Rect(3f * contentWidth / 4f, i * entryHeight, (viewContentWidth / 4f) - 20f, entryHeight), "X"))
{
DeleteKeyframe(i);
break;
}
- //line++;
}
GUI.color = origGuiColor;
}
GUI.EndScrollView();
- line += 6;
- line += 0.5f;
- if(GUI.Button(new Rect(leftIndent, contentTop + (line * entryHeight), 3 * contentWidth / 4, entryHeight), "New Key"))
- {
- CreateNewKeyframe();
- }
+ line += 5.25f;
+ if (GUI.Button(ThinRect(++line), Localize("NewKey"))) { CreateNewKeyframe(); }
+ if (cameraToolActive && GUI.Button(ThinRect(++line), Localize("ToStationaryCamera"))) { FromPathingToStationary(); }
+ if (GUI.Button(ThinHalfRect(++line, 0), Localize("ResetRoll"))) ResetRoll();
}
}
- line += 1.25f;
-
- enableKeypad = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), enableKeypad, "Keypad Control");
- if(enableKeypad)
+ line += 0.25f;
+ enableKeypad = GUI.Toggle(ThinRect(++line), enableKeypad, Localize("KeypadControl"));
+ if (enableKeypad)
{
- line++;
-
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth / 2, entryHeight), "Move Speed:");
- guiFreeMoveSpeed = GUI.TextField(new Rect(leftIndent + contentWidth / 2, contentTop + (line * entryHeight), contentWidth / 2, entryHeight), guiFreeMoveSpeed);
- if(float.TryParse(guiFreeMoveSpeed, out parseResult))
+ GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), Localize("MoveSpeed"));
+ if (!textInput)
{
- freeMoveSpeed = Mathf.Abs(parseResult);
- guiFreeMoveSpeed = freeMoveSpeed.ToString();
+ freeMoveSpeedRaw = Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, contentWidth / 2f - 30f), freeMoveSpeedRaw, freeMoveSpeedMinRaw, freeMoveSpeedMaxRaw) * 100f) / 100f;
+ freeMoveSpeed = Mathf.Pow(10f, freeMoveSpeedRaw);
+ GUI.Label(SliderLabelRight(line), freeMoveSpeed.ToString("G4"));
}
-
- line++;
-
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth / 2, entryHeight), "Zoom Speed:");
- guiKeyZoomSpeed = GUI.TextField(new Rect(leftIndent + contentWidth / 2, contentTop + (line * entryHeight), contentWidth / 2, entryHeight), guiKeyZoomSpeed);
- if(float.TryParse(guiKeyZoomSpeed, out parseResult))
+ else
{
- keyZoomSpeed = Mathf.Abs(parseResult);
- guiKeyZoomSpeed = keyZoomSpeed.ToString();
+ inputFields["freeMoveSpeed"].tryParseValue(GUI.TextField(RightRect(line), inputFields["freeMoveSpeed"].possibleValue, 8, inputFieldStyle));
+ freeMoveSpeed = inputFields["freeMoveSpeed"].currentValue;
}
- }
- else
- {
- line++;
- line++;
- }
-
- line++;
- line++;
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Keys:", centerLabel);
- line++;
- //activate key binding
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Activate: ", leftLabel);
- GUI.Label(new Rect(leftIndent + 60, contentTop + (line * entryHeight), 60, entryHeight), cameraKey, leftLabel);
- if(!isRecordingInput)
- {
- if(GUI.Button(new Rect(leftIndent + 125, contentTop + (line * entryHeight), 100, entryHeight), "Bind Key"))
+ GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), Localize("ZoomSpeed"));
+ if (!textInput)
{
- mouseUp = false;
- isRecordingInput = true;
- isRecordingActivate = true;
+ zoomSpeedRaw = Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, contentWidth / 2f - 30f), zoomSpeedRaw, zoomSpeedMinRaw, zoomSpeedMaxRaw) * 100f) / 100f;
+ keyZoomSpeed = Mathf.Pow(10f, zoomSpeedRaw);
+ GUI.Label(SliderLabelRight(line), keyZoomSpeed.ToString("G3"));
}
- }
- else if(mouseUp && isRecordingActivate)
- {
- GUI.Label(new Rect(leftIndent + 125, contentTop + (line * entryHeight), 100, entryHeight), "Press a Key", leftLabel);
-
- string inputString = CCInputUtils.GetInputString();
- if(inputString.Length > 0)
+ else
{
- cameraKey = inputString;
- isRecordingInput = false;
- isRecordingActivate = false;
+ inputFields["keyZoomSpeed"].tryParseValue(GUI.TextField(RightRect(line), inputFields["keyZoomSpeed"].possibleValue, 8, inputFieldStyle));
+ keyZoomSpeed = inputFields["keyZoomSpeed"].currentValue;
}
+
+ GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), Localize("ControlMode"));
+ fmMode = (FMModeTypes)Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, contentWidth / 2f - 25f, -25f), (int)fmMode, 0, FMModeTypesMax));
+ GUI.Label(SliderLabelRight(line, 25f), fmMode.ToString());
}
- line++;
+ GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), Localize("PivotMode"));
+ fmPivotMode = (FMPivotModes)Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, contentWidth / 2f - 25f, -25f), (int)fmPivotMode, 0, FMPivotModeMax));
+ GUI.Label(SliderLabelRight(line, 25f), fmPivotMode.ToString());
+ ++line;
- //revert key binding
- GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Revert: ", leftLabel);
- GUI.Label(new Rect(leftIndent + 60, contentTop + (line * entryHeight), 60, entryHeight), revertKey);
- if(!isRecordingInput)
- {
- if(GUI.Button(new Rect(leftIndent + 125, contentTop + (line * entryHeight), 100, entryHeight), "Bind Key"))
- {
- mouseUp = false;
- isRecordingInput = true;
- isRecordingRevert = true;
- }
- }
- else if(mouseUp && isRecordingRevert)
+ // Key bindings
+ if (GUI.Button(LabelRect(++line), Localize("EditKeybindings")))
+ { editingKeybindings = !editingKeybindings; }
+ if (editingKeybindings)
{
- GUI.Label(new Rect(leftIndent + 125, contentTop + (line * entryHeight), 100, entryHeight), "Press a Key", leftLabel);
- string inputString = CCInputUtils.GetInputString();
- if(inputString.Length > 0)
- {
- revertKey = inputString;
- isRecordingInput = false;
- isRecordingRevert = false;
- }
+ cameraKey = KeyBinding(cameraKey, LocalizeStr("Activate"), ++line);
+ revertKey = KeyBinding(revertKey, LocalizeStr("Revert"), ++line);
+ toggleMenu = KeyBinding(toggleMenu, LocalizeStr("Menu"), ++line);
+ fmUpKey = KeyBinding(fmUpKey, LocalizeStr("Up"), ++line);
+ fmDownKey = KeyBinding(fmDownKey, LocalizeStr("Down"), ++line);
+ fmForwardKey = KeyBinding(fmForwardKey, LocalizeStr("Forward"), ++line);
+ fmBackKey = KeyBinding(fmBackKey, LocalizeStr("Back"), ++line);
+ fmLeftKey = KeyBinding(fmLeftKey, LocalizeStr("Left"), ++line);
+ fmRightKey = KeyBinding(fmRightKey, LocalizeStr("Right"), ++line);
+ fmZoomInKey = KeyBinding(fmZoomInKey, LocalizeStr("ZoomIn"), ++line);
+ fmZoomOutKey = KeyBinding(fmZoomOutKey, LocalizeStr("ZoomOut"), ++line);
+ fmMovementModifier = KeyBinding(fmMovementModifier, LocalizeStr("Modifier"), ++line);
+ fmModeToggleKey = KeyBinding(fmModeToggleKey, LocalizeStr("FreeMoveMode"), ++line);
+ fmPivotModeKey = KeyBinding(fmPivotModeKey, LocalizeStr("PivotMode"), ++line);
+ resetRollKey = KeyBinding(resetRollKey, LocalizeStr("ResetRoll"), ++line);
}
- line++;
- line++;
- Rect saveRect = new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth / 2, entryHeight);
- if(GUI.Button(saveRect, "Save"))
+ Rect saveRect = HalfRect(++line, 0);
+ if (GUI.Button(saveRect, Localize("Save")))
+ { Save(); }
+
+ Rect loadRect = HalfRect(line, 1);
+ if (GUI.Button(loadRect, Localize("Reload")))
{
- Save();
+ if (isPlayingPath) StopPlayingPath();
+ Load();
}
- Rect loadRect = new Rect(saveRect);
- loadRect.x += contentWidth / 2;
- if(GUI.Button(loadRect, "Reload"))
+ float timeSinceLastSaved = Time.unscaledTime - lastSavedTime;
+ if (timeSinceLastSaved < 1)
{
- Load();
+ ++line;
+ GUI.Label(LabelRect(++line), timeSinceLastSaved < 0.5 ? LocalizeStr("Saving") : LocalizeStr("Saved"), centerLabel);
}
-
+
//fix length
- windowHeight = contentTop+(line*entryHeight)+entryHeight+entryHeight;
+ windowHeight = contentTop + (line * entryHeight) + entryHeight + entryHeight;
windowRect.height = windowHeight;// = new Rect(windowRect.x, windowRect.y, windowWidth, windowHeight);
+
+ // Tooltips
+ if (ShowTooltips && Event.current.type == EventType.Repaint && !string.IsNullOrEmpty(GUI.tooltip)) Tooltips.SetTooltip(GUI.tooltip, Event.current.mousePosition * _UIScale + windowRect.position);
}
- public static string pathSaveURL = "GameData/CameraTools/paths.cfg";
- void Save()
+ string KeyBinding(string current, string label, float line)
{
- CTPersistantField.Save();
-
- ConfigNode pathFileNode = ConfigNode.Load(pathSaveURL);
- ConfigNode pathsNode = pathFileNode.GetNode("CAMERAPATHS");
- pathsNode.RemoveNodes("CAMERAPATH");
-
- foreach(var path in availablePaths)
+ GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), $"{label}: ", leftLabel);
+ GUI.Label(new Rect(leftIndent + 70, contentTop + (line * entryHeight), 50, entryHeight), current, leftLabel);
+ if (!isRecordingInput || label != currentlyBinding)
{
- path.Save(pathsNode);
+ if (GUI.Button(new Rect(leftIndent + 130, contentTop + (line * entryHeight), 95, entryHeight), Localize("BindKey")))
+ {
+ mouseUp = false;
+ isRecordingInput = true;
+ currentlyBinding = label;
+ }
}
- pathFileNode.Save(pathSaveURL);
- }
-
- void Load()
- {
- CTPersistantField.Load();
- guiOffsetForward = manualOffsetForward.ToString();
- guiOffsetRight = manualOffsetRight.ToString();
- guiOffsetUp = manualOffsetUp.ToString();
- guiKeyZoomSpeed = keyZoomSpeed.ToString();
- guiFreeMoveSpeed = freeMoveSpeed.ToString();
-
- DeselectKeyframe();
- selectedPathIndex = -1;
- availablePaths = new List();
- ConfigNode pathFileNode = ConfigNode.Load(pathSaveURL);
- foreach(var node in pathFileNode.GetNode("CAMERAPATHS").GetNodes("CAMERAPATH"))
+ else if (mouseUp)
{
- availablePaths.Add(CameraPath.Load(node));
+ GUI.Label(new Rect(leftIndent + 140, contentTop + (line * entryHeight), 85, entryHeight), Localize("PressAKey"), leftLabel);
+
+ string inputString = GetInputString();
+ if (inputString.Length > 0)
+ {
+ isRecordingInput = false;
+ boundThisFrame = true;
+ currentlyBinding = "";
+ if (inputString != "escape") // Allow escape key to cancel keybinding.
+ { return inputString; }
+ }
}
+
+ return current;
}
void KeyframeEditorWindow()
{
- float width = 300;
- float height = 130;
- Rect kWindowRect = new Rect(windowRect.x - width, windowRect.y + 365, width, height);
+ float width = 300f;
+ float gap = 5;
+ float lineHeight = 25f;
+ float line = 0f;
+ Rect kWindowRect = new Rect(windowRect.x - width, windowRect.y + 365, width, keyframeEditorWindowHeight);
GUI.Box(kWindowRect, string.Empty);
GUI.BeginGroup(kWindowRect);
- GUI.Label(new Rect(5, 5, 100, 25), "Keyframe #"+currentKeyframeIndex);
- if(GUI.Button(new Rect(105, 5, 180, 25), "Revert Pos"))
+ GUI.Label(new Rect(gap, gap, 100, lineHeight - gap), Localize("KeyframeNum", currentKeyframeIndex.ToString()));
+ if (GUI.Button(new Rect(100 + gap, gap, 200 - 2 * gap, lineHeight), Localize("RevertPos")))
{
ViewKeyframe(currentKeyframeIndex);
}
- GUI.Label(new Rect(5, 35, 80, 25), "Time: ");
- currKeyTimeString = GUI.TextField(new Rect(100, 35, 195, 25), currKeyTimeString, 16);
+ GUI.Label(new Rect(gap, gap + (++line * lineHeight), 80, lineHeight - gap), Localize("Time"));
+ currKeyTimeString = GUI.TextField(new Rect(100 + gap, gap + line * lineHeight, 200 - 2 * gap, lineHeight - gap), currKeyTimeString, 16);
float parsed;
- if(float.TryParse(currKeyTimeString, out parsed))
+ if (float.TryParse(currKeyTimeString, out parsed))
{
currentKeyframeTime = parsed;
}
+ if (currentKeyframeIndex > 1)
+ {
+ if (GUI.Button(new Rect(100 + gap, gap + (++line * lineHeight), 200 - 2 * gap, lineHeight - gap), Localize("MaintainSpeed")))
+ {
+ CameraKeyframe previousKeyframe = currentPath.GetKeyframe(currentKeyframeIndex - 1);
+ CameraKeyframe previousPreviousKeyframe = currentPath.GetKeyframe(currentKeyframeIndex - 2);
+ float previousKeyframeDistance = Vector3.Distance(previousKeyframe.position, previousPreviousKeyframe.position);
+ float previousKeyframeDuration = previousKeyframe.time - previousPreviousKeyframe.time;
+ float previousKeyframeSpeed = previousKeyframeDistance / previousKeyframeDuration;
+ float currentKeyFrameDistance = Vector3.Distance(flightCamera.transform.localPosition, previousKeyframe.position);
+ float adjustedDuration = currentKeyFrameDistance / previousKeyframeSpeed;
+ float currentKeyframeDuration = currentKeyframeTime - previousKeyframe.time;
+ currentKeyframeTime += adjustedDuration - currentKeyframeDuration;
+ currKeyTimeString = currentKeyframeTime.ToString();
+ }
+ }
+ GUI.Label(new Rect(gap, gap + (++line * lineHeight), 100, lineHeight - gap), Localize("PositionInterpolation", currentKeyframePositionInterpolationType.ToString()));
+ currentKeyframePositionInterpolationType = (PositionInterpolationType)Mathf.RoundToInt(GUI.HorizontalSlider(new Rect(100 + 2 * gap, gap + line * lineHeight, 200 - 3 * gap, lineHeight - gap), (float)currentKeyframePositionInterpolationType, 0, PositionInterpolationTypeMax));
+ GUI.Label(new Rect(gap, gap + (++line * lineHeight), 100, lineHeight - gap), Localize("RotationInterpolation", currentKeyframeRotationInterpolationType.ToString()));
+ currentKeyframeRotationInterpolationType = (RotationInterpolationType)Mathf.RoundToInt(GUI.HorizontalSlider(new Rect(100 + 2 * gap, gap + line * lineHeight, 200 - 3 * gap, lineHeight - gap), (float)currentKeyframeRotationInterpolationType, 0, RotationInterpolationTypeMax));
bool applied = false;
- if(GUI.Button(new Rect(100, 65, 195, 25), "Apply"))
+ if (GUI.Button(new Rect(100 + gap, gap + (++line * lineHeight), 200 - 2 * gap, lineHeight - gap), Localize("Apply")))
{
- Debug.Log("Applying keyframe at time: " + currentKeyframeTime);
- currentPath.SetTransform(currentKeyframeIndex, flightCamera.transform, zoomExp, currentKeyframeTime);
+ Debug.Log("[CameraTools]: Applying keyframe at time: " + currentKeyframeTime);
+ currentPath.SetTransform(currentKeyframeIndex, flightCamera.transform, zoomExp, ref currentKeyframeTime, currentKeyframePositionInterpolationType, currentKeyframeRotationInterpolationType);
applied = true;
}
- if(GUI.Button(new Rect(100, 105, 195, 20), "Cancel"))
+ if (GUI.Button(new Rect(100 + gap, gap + (++line * lineHeight), 200 - 2 * gap, lineHeight - gap), Localize("Cancel")))
{
applied = true;
}
GUI.EndGroup();
- if(applied)
+ if (applied)
{
DeselectKeyframe();
}
+ keyframeEditorWindowHeight = 2 * gap + (++line * lineHeight);
+
+ // Tooltips
+ if (ShowTooltips && Event.current.type == EventType.Repaint && !string.IsNullOrEmpty(GUI.tooltip)) Tooltips.SetTooltip(GUI.tooltip, Event.current.mousePosition);
}
bool showPathSelectorWindow = false;
@@ -1773,23 +3769,24 @@ void PathSelectorWindow()
float height = 300;
float indent = 5;
float scrollRectSize = width - indent - indent;
- Rect pSelectRect = new Rect(windowRect.x - width, windowRect.y + 290, width, height);
+ Rect pSelectRect = new Rect(windowRect.x - width, windowRect.y + 290, width, height);
GUI.Box(pSelectRect, string.Empty);
GUI.BeginGroup(pSelectRect);
Rect scrollRect = new Rect(indent, indent, scrollRectSize, scrollRectSize);
float scrollHeight = Mathf.Max(scrollRectSize, entryHeight * availablePaths.Count);
- Rect scrollViewRect = new Rect(0, 0, scrollRectSize-20, scrollHeight);
+ Rect scrollViewRect = new Rect(0, 0, scrollRectSize - 20, scrollHeight);
pathSelectScrollPos = GUI.BeginScrollView(scrollRect, pathSelectScrollPos, scrollViewRect);
bool selected = false;
- for(int i = 0; i < availablePaths.Count; i++)
+ for (int i = 0; i < availablePaths.Count; i++)
{
- if(GUI.Button(new Rect(0, i * entryHeight, scrollRectSize - 90, entryHeight), availablePaths[i].pathName))
+ if (GUI.Button(new Rect(0, i * entryHeight, scrollRectSize - 90, entryHeight), availablePaths[i].pathName))
{
SelectPath(i);
selected = true;
+ if (cameraToolActive && currentPath.keyframeCount > 0) PlayPathingCam();
}
- if(GUI.Button(new Rect(scrollRectSize-80, i * entryHeight, 60, entryHeight), "Delete"))
+ if (GUI.Button(new Rect(scrollRectSize - 80, i * entryHeight, 60, entryHeight), Localize("Delete")))
{
DeletePath(i);
break;
@@ -1799,496 +3796,428 @@ void PathSelectorWindow()
GUI.EndScrollView();
GUI.EndGroup();
- if(selected)
+ if (selected)
{
showPathSelectorWindow = false;
}
+
+ // Tooltips
+ if (ShowTooltips && Event.current.type == EventType.Repaint && !string.IsNullOrEmpty(GUI.tooltip)) Tooltips.SetTooltip(GUI.tooltip, Event.current.mousePosition);
}
void DrawIncrementButtons(Rect fieldRect, ref float val)
{
- Rect incrButtonRect = new Rect(fieldRect.x-incrButtonWidth, fieldRect.y, incrButtonWidth, entryHeight);
- if(GUI.Button(incrButtonRect, "-"))
+ Rect incrButtonRect = new Rect(fieldRect.x - incrButtonWidth, fieldRect.y, incrButtonWidth, entryHeight);
+ if (GUI.Button(incrButtonRect, "-"))
{
val -= 5;
}
incrButtonRect.x -= incrButtonWidth;
- if(GUI.Button(incrButtonRect, "--"))
+ if (GUI.Button(incrButtonRect, "--"))
{
val -= 50;
}
incrButtonRect.x = fieldRect.x + fieldRect.width;
- if(GUI.Button(incrButtonRect, "+"))
+ if (GUI.Button(incrButtonRect, "+"))
{
val += 5;
}
incrButtonRect.x += incrButtonWidth;
- if(GUI.Button(incrButtonRect, "++"))
+ if (GUI.Button(incrButtonRect, "++"))
{
val += 50;
}
}
-
+
//AppLauncherSetup
void AddToolbarButton()
{
- if(!hasAddedButton)
+ if (!hasAddedButton)
{
Texture buttonTexture = GameDatabase.Instance.GetTexture("CameraTools/Textures/icon", false);
- ApplicationLauncher.Instance.AddModApplication(EnableGui, DisableGui, Dummy, Dummy, Dummy, Dummy, ApplicationLauncher.AppScenes.FLIGHT, buttonTexture);
+ ApplicationLauncher.Instance.AddModApplication(ToggleGui, ToggleGui, Dummy, Dummy, Dummy, Dummy, ApplicationLauncher.AppScenes.FLIGHT, buttonTexture);
CamTools.hasAddedButton = true;
}
-
+
+ }
+
+ void ToggleGui()
+ {
+ if (guiEnabled)
+ DisableGui();
+ else
+ EnableGui();
}
-
+
void EnableGui()
{
guiEnabled = true;
- Debug.Log ("Showing CamTools GUI");
+ // Debug.Log("[CameraTools]: Showing CamTools GUI");
}
-
+
void DisableGui()
{
- guiEnabled = false;
- Debug.Log ("Hiding CamTools GUI");
+ guiEnabled = false;
+ Save();
+ // Debug.Log("[CameraTools]: Hiding CamTools GUI");
}
-
+
void Dummy()
- {}
-
+ { }
+
void GameUIEnable()
{
- gameUIToggle = true;
+ gameUIToggle = true;
}
-
+
void GameUIDisable()
{
- gameUIToggle = false;
- }
-
- void CycleReferenceMode(bool forward)
- {
- var length = System.Enum.GetValues(typeof(ReferenceModes)).Length;
- if(forward)
- {
- referenceMode++;
- if((int)referenceMode == length) referenceMode = 0;
- }
- else
- {
- referenceMode--;
- if((int)referenceMode == -1) referenceMode = (ReferenceModes) length-1;
- }
+ gameUIToggle = false;
}
void CycleToolMode(bool forward)
{
var length = System.Enum.GetValues(typeof(ToolModes)).Length;
- if(forward)
+ if (forward)
{
toolMode++;
- if((int)toolMode == length) toolMode = 0;
+ if ((int)toolMode == length) toolMode = 0;
}
else
{
toolMode--;
- if((int)toolMode == -1) toolMode = (ToolModes) length-1;
+ if ((int)toolMode == -1) toolMode = (ToolModes)length - 1;
}
+ if (toolMode != ToolModes.Pathing)
+ { DeselectKeyframe(); }
}
-
- void OnFloatingOriginShift(Vector3d offset, Vector3d data1)
- {
- /*
- Debug.LogWarning ("======Floating origin shifted.======");
- Debug.LogWarning ("======Passed offset: "+offset+"======");
- Debug.LogWarning ("======FloatingOrigin offset: "+FloatingOrigin.fetch.offset+"======");
- Debug.LogWarning("========Floating Origin threshold: "+FloatingOrigin.fetch.threshold+"==========");
- */
- }
+ #endregion
- void UpdateLoadedVessels()
+ #region Utils
+ void CurrentVesselWillDestroy(Vessel v)
{
- if(loadedVessels == null)
+ if (!hasDied && cameraToolActive && vessel == v)
{
- loadedVessels = new List();
- }
- else
- {
- loadedVessels.Clear();
- }
-
- foreach(var v in FlightGlobals.Vessels)
- {
- if(v.loaded && v.vesselType != VesselType.Debris && !v.isActiveVessel)
- {
- loadedVessels.Add(v);
- }
- }
- }
+ hasDied = true;
+ deathCamTarget = null;
- private void CheckForBDAI(Vessel v)
- {
- hasBDAI = false;
- aiComponent = null;
- if(v)
- {
- foreach(Part p in v.parts)
+ if (toolMode == ToolModes.DogfightCamera)
{
- if(p.GetComponent("BDModulePilotAI"))
+ // Something borks the camera position/rotation when the target/parent is set to none/null. This fixes that.
+ float atmoFactor = (float)(vessel.atmDensity / FlightGlobals.GetBodyByName("Kerbin").atmDensityASL); // 0 in space, 1 at Kerbin sea level.
+ float alpha = 0, beta = 0;
+ if (bdArmory.isBDMissile)
+ {
+ if (dogfightTarget != null)
+ {
+ var distanceSqr = (vessel.CoM - dogfightTarget.CoM).sqrMagnitude - flightCamera.Distance * flightCamera.Distance / 4f;
+ alpha = Mathf.Clamp01(distanceSqr / 1e4f); // Within 100m, start at close to the target's velocity
+ beta = Mathf.Clamp01(distanceSqr / 4f / (float)(v.Velocity() - dogfightTarget.Velocity()).sqrMagnitude); // Within 2s, end at close to the target's velocity
+ deathCamVelocity = (1 - atmoFactor / 2) * ((1 - alpha) * dogfightTarget.Velocity() + alpha * vessel.Velocity());
+ deathCamTargetVelocity = (1 - atmoFactor) * ((1 - beta) * dogfightTarget.Velocity() + beta * vessel.Velocity());
+ deathCamDecayFactor = 0.9f - 0.2f * Mathf.Clamp01(atmoFactor);
+ deathCamTarget = dogfightTarget;
+ }
+ else
+ {
+ deathCamVelocity = (1 - Mathf.Clamp01(atmoFactor) / 2) * vessel.Velocity();
+ deathCamTargetVelocity = (1 - Mathf.Clamp01(atmoFactor)) * deathCamVelocity;
+ deathCamDecayFactor = 1 / (1 + atmoFactor); // Same as the explosion decay rate in BDA.
+ }
+ }
+ else
+ {
+ deathCamVelocity = vessel.radarAltitude < 10d ? Vector3d.zero : (1 - Mathf.Clamp01(atmoFactor) / 2) * vessel.Velocity(); // Track the explosion a bit.
+ deathCamTargetVelocity = (1 - Mathf.Clamp01(atmoFactor)) * deathCamVelocity;
+ deathCamDecayFactor = 1 / (1 + atmoFactor / 2); // Slower than the explosion decay rate in BDA.
+ }
+ if (DEBUG)
{
- hasBDAI = true;
- aiComponent = (object)p.GetComponent("BDModulePilotAI");
- return;
+ message = $"Activating death camera with speed {deathCamVelocity.magnitude:F1}m/s, target speed {deathCamTargetVelocity.magnitude:F1}m/s and decay factor {deathCamDecayFactor:F3} (missile: {bdArmory.isBDMissile} ({v.Velocity().magnitude:F1}), target: {(dogfightTarget ? $"{dogfightTarget.vesselName} ({dogfightTarget.Velocity().magnitude:F1})" : "null")}, atmoFactor: {atmoFactor}, alpha: {alpha}, beta: {beta}).";
}
}
+ else
+ {
+ deathCamVelocity = Vector3.zero;
+ deathCamTargetVelocity = Vector3.zero;
+ if (DEBUG) message = $"Activating stationary death camera for camera mode {toolMode}.";
+ }
+ if (DEBUG)
+ {
+ Debug.Log("[CameraTools]: " + message);
+ DebugLog(message);
+ }
}
}
- private Vessel GetAITargetedVessel()
+ Part GetPartFromMouse()
{
- if(!hasBDAI || aiComponent==null || bdAiTargetField==null)
+ Vector3 mouseAim = new Vector3(Input.mousePosition.x / Screen.width, Input.mousePosition.y / Screen.height, 0);
+ Ray ray = FlightCamera.fetch.mainCamera.ViewportPointToRay(mouseAim);
+ RaycastHit hit;
+ if (Physics.Raycast(ray, out hit, 10000, (int)(LayerMasks.Parts | LayerMasks.EVA | LayerMasks.Wheels)))
{
- return null;
+ Part p = hit.transform.GetComponentInParent();
+ return p;
}
-
- return (Vessel) bdAiTargetField.GetValue(aiComponent);
+ else return null;
}
- private Type AIModuleType()
+ Vector3 GetPosFromMouse()
{
- //Debug.Log("loaded assy's: ");
- foreach(var assy in AssemblyLoader.loadedAssemblies)
+ Vector3 mouseAim = new Vector3(Input.mousePosition.x / Screen.width, Input.mousePosition.y / Screen.height, 0);
+ Ray ray = FlightCamera.fetch.mainCamera.ViewportPointToRay(mouseAim);
+ RaycastHit hit;
+ if (Physics.Raycast(ray, out hit, 15000, 557057))
{
- //Debug.Log("- "+assy.assembly.FullName);
- if(assy.assembly.FullName.Contains("BDArmory"))
- {
- foreach(var t in assy.assembly.GetTypes())
- {
- if(t.Name == "BDModulePilotAI")
- {
- return t;
- }
- }
- }
+ return hit.point - (10 * ray.direction);
}
-
- return null;
+ else return Vector3.zero;
}
- private FieldInfo GetAITargetField()
+ void UpdateLoadedVessels()
{
- Type aiModType = AIModuleType();
- if(aiModType == null) return null;
-
- FieldInfo[] fields = aiModType.GetFields(BindingFlags.NonPublic|BindingFlags.Instance);
- //Debug.Log("bdai fields: ");
- foreach(var f in fields)
+ if (loadedVessels == null)
{
- //Debug.Log("- " + f.Name);
- if(f.Name == "targetVessel")
- {
- return f;
- }
+ loadedVessels = new List();
+ }
+ else
+ {
+ loadedVessels.Clear();
}
- return null;
- }
-
-
- void SwitchToVessel(Vessel v)
- {
- vessel = v;
-
- CheckForBDAI(v);
-
- if(cameraToolActive)
+ foreach (Vessel v in FlightGlobals.Vessels)
{
- if(toolMode == ToolModes.DogfightCamera)
- {
- StartCoroutine(ResetDogfightCamRoutine());
- }
+ if (v == null || !v.loaded || v.packed) continue;
+ if (v.vesselType == VesselType.Debris || v.isActiveVessel) continue; // Ignore debris and the active vessel.
+ loadedVessels.Add(v);
}
}
- IEnumerator ResetDogfightCamRoutine()
+ private string GetVersion()
{
- yield return new WaitForEndOfFrame();
- RevertCamera();
- StartDogfightCamera();
+ try
+ {
+ Version = this.GetType().Assembly.GetName().Version.ToString(3);
+ }
+ catch (Exception e)
+ {
+ Debug.LogWarning($"[CameraTools]: Failed to get version string: {e.Message}");
+ }
+ return Version;
}
- void CreateNewPath()
+ public static float GetRadarAltitudeAtPos(Vector3 position)
{
- showKeyframeEditor = false;
- availablePaths.Add(new CameraPath());
- selectedPathIndex = availablePaths.Count - 1;
+ var geoCoords = FlightGlobals.currentMainBody.GetLatitudeAndLongitude(position);
+ var altitude = FlightGlobals.currentMainBody.GetAltitude(position);
+ var terrainAltitude = FlightGlobals.currentMainBody.TerrainAltitude(geoCoords.x, geoCoords.y);
+ return (float)(altitude - Math.Max(terrainAltitude, 0));
}
- void DeletePath(int index)
+ public float GetTime()
{
- if(index < 0) return;
- if(index >= availablePaths.Count) return;
- availablePaths.RemoveAt(index);
- selectedPathIndex = -1;
+ return useRealTime ? Time.unscaledTime : Time.time;
}
- void SelectPath(int index)
+ public void SetZoomImmediate(float zoom)
{
- selectedPathIndex = index;
+ zoomExp = zoom;
+ zoomFactor = Mathf.Exp(zoomExp) / Mathf.Exp(1);
+ manualFOV = 60 / zoomFactor;
+ currentFOV = manualFOV;
+ flightCamera.SetFoV(currentFOV);
}
- void SelectKeyframe(int index)
+ void SetCameraParent(Transform referenceTransform, bool resetToCoM = false)
{
- if(isPlayingPath)
+ if (DEBUG) Debug.Log($"[CameraTools]: Setting the camera parent to {cameraParent.transform.name} using {referenceTransform.name} on {referenceTransform.gameObject.name} as reference. Previously was {flightCamera.transform.parent.name} on {flightCamera.gameObject.name}, reset-to-CoM: {resetToCoM}");
+ var position = referenceTransform.position; // Take local copies in case we were passed the cameraParent as the reference.
+ var rotation = referenceTransform.rotation;
+ cameraParent.transform.SetPositionAndRotation(position, rotation);
+ flightCamera.SetTargetNone();
+ flightCamera.transform.parent = cameraParent.transform;
+ cameraParentWasStolen = false;
+ flightCamera.DeactivateUpdate();
+ if (resetToCoM)
{
- StopPlayingPath();
+ cameraParent.transform.position = vessel.CoM; // Then adjust the flightCamera for the new parent.
+ flightCamera.transform.localPosition = cameraParent.transform.InverseTransformPoint(position);
+ flightCamera.transform.localRotation = Quaternion.identity;
}
- currentKeyframeIndex = index;
- UpdateCurrentValues();
- showKeyframeEditor = true;
- ViewKeyframe(currentKeyframeIndex);
+ origParent.position = enableVFX ? cameraParent.transform.position : FlightGlobals.currentMainBody.position;
}
- void DeselectKeyframe()
+ void UpdateDeathCamFromFlight()
{
- currentKeyframeIndex = -1;
- showKeyframeEditor = false;
+ deathCamPosition = flightCamera.transform.position;
+ deathCamRotation = flightCamera.transform.rotation;
+ deathCam.transform.SetPositionAndRotation(deathCamPosition, deathCamRotation);
}
- void DeleteKeyframe(int index)
+ void SetDeathCam()
{
- currentPath.RemoveKeyframe(index);
- if(index == currentKeyframeIndex)
+ if (DEBUG && flightCamera.transform.parent != deathCam.transform)
{
- DeselectKeyframe();
- }
- if(currentPath.keyframeCount > 0 && currentKeyframeIndex >= 0)
- {
- SelectKeyframe(Mathf.Clamp(currentKeyframeIndex, 0, currentPath.keyframeCount - 1));
+ message = $"Setting the death camera from {flightCamera.transform.parent.name}.";
+ DebugLog(message);
+ Debug.Log($"[CameraTools]: {message}");
}
+ flightCamera.SetTargetNone();
+ deathCam.transform.SetPositionAndRotation(deathCamPosition, deathCamRotation);
+ flightCamera.transform.parent = deathCam.transform;
+ cameraParentWasStolen = false;
+ flightCamera.DeactivateUpdate();
+ flightCamera.transform.localPosition = Vector3.zero; // We manipulate the deathCam transform and leave the flightCamera transform local values at their defaults.
+ flightCamera.transform.localRotation = Quaternion.identity;
+ flightCamera.SetFoV(currentFOV); // Set the FOV back to whatever we last had (when the camera parent gets stolen, this reverts).
}
- void UpdateCurrentValues()
+ ///
+ /// Get input from the standard camera axes.
+ ///
+ /// Scale the output.
+ /// Negate the output.
+ ///
+ Vector2 GetControllerInput(float scale = 2f, bool inverted = false)
{
- if(currentPath == null) return;
- if(currentKeyframeIndex < 0 || currentKeyframeIndex >= currentPath.keyframeCount)
- {
- return;
- }
- CameraKeyframe currentKey = currentPath.GetKeyframe(currentKeyframeIndex);
- currentKeyframeTime = currentKey.time;
-
- currKeyTimeString = currentKeyframeTime.ToString();
+ if (inverted) scale = -scale;
+ return new(
+ scale * GameSettings.AXIS_CAMERA_HDG.GetAxis(),
+ -scale * GameSettings.AXIS_CAMERA_PITCH.GetAxis()
+ );
}
- void CreateNewKeyframe()
+ public static bool GameIsPaused
{
- if(!cameraToolActive)
- {
- StartPathingCam();
- }
-
- showPathSelectorWindow = false;
-
- float time = currentPath.keyframeCount > 0 ? currentPath.GetKeyframe(currentPath.keyframeCount - 1).time + 1 : 0;
- currentPath.AddTransform(flightCamera.transform, zoomExp, time);
- SelectKeyframe(currentPath.keyframeCount - 1);
-
- if(currentPath.keyframeCount > 6)
- {
- keysScrollPos.y += entryHeight;
- }
+ get { return PauseMenu.isOpen || Time.timeScale == 0; }
}
+ #endregion
- void ViewKeyframe(int index)
+ #region Load/Save
+ void Save()
{
- if(!cameraToolActive)
- {
- StartPathingCam();
- }
- CameraKeyframe currentKey = currentPath.GetKeyframe(index);
- flightCamera.transform.localPosition = currentKey.position;
- flightCamera.transform.localRotation = currentKey.rotation;
- zoomExp = currentKey.zoom;
- }
+ CTPersistantField.Save("CToolsSettings", typeof(CamTools), this);
- void StartPathingCam()
- {
- vessel = FlightGlobals.ActiveVessel;
- cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.GetWorldPos3D()).normalized;
- if(FlightCamera.fetch.mode == FlightCamera.Modes.ORBITAL || (FlightCamera.fetch.mode == FlightCamera.Modes.AUTO && FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.ORBITAL))
- {
- cameraUp = Vector3.up;
- }
+ ConfigNode pathFileNode = ConfigNode.Load(CameraPath.pathSaveURL);
- cameraParent.transform.position = vessel.transform.position+vessel.rb_velocity*Time.fixedDeltaTime;
- cameraParent.transform.rotation = vessel.transform.rotation;
- flightCamera.SetTargetNone();
- flightCamera.transform.parent = cameraParent.transform;
- flightCamera.DeactivateUpdate();
+ if (pathFileNode == null)
+ pathFileNode = new ConfigNode();
- cameraToolActive = true;
- }
+ if (!pathFileNode.HasNode("CAMERAPATHS"))
+ pathFileNode.AddNode("CAMERAPATHS");
- void PlayPathingCam()
- {
- if(selectedPathIndex < 0)
- {
- RevertCamera();
- return;
- }
+ ConfigNode pathsNode = pathFileNode.GetNode("CAMERAPATHS");
+ pathsNode.RemoveNodes("CAMERAPATH");
- if(currentPath.keyframeCount <= 0)
+ foreach (var path in availablePaths)
{
- RevertCamera();
- return;
+ path.Save(pathsNode);
}
-
- DeselectKeyframe();
-
- if(!cameraToolActive)
+ if (!Directory.GetParent(CameraPath.pathSaveURL).Exists)
+ { Directory.GetParent(CameraPath.pathSaveURL).Create(); }
+ var success = pathFileNode.Save(CameraPath.pathSaveURL);
+ if (success)
{
- StartPathingCam();
- }
-
- CameraTransformation firstFrame = currentPath.Evaulate(0);
- flightCamera.transform.localPosition = firstFrame.position;
- flightCamera.transform.localRotation = firstFrame.rotation;
- zoomExp = firstFrame.zoom;
-
- isPlayingPath = true;
- pathStartTime = Time.time;
- }
-
- void StopPlayingPath()
- {
- isPlayingPath = false;
- }
+ lastSavedTime = Time.unscaledTime;
- void TogglePathList()
- {
- showKeyframeEditor = false;
- showPathSelectorWindow = !showPathSelectorWindow;
+ if (File.Exists(CameraPath.oldPathSaveURL))
+ { File.Delete(CameraPath.oldPathSaveURL); } // Remove the old settings if it exists and the new settings were saved.
+ }
}
- void UpdatePathingCam()
+ void Load()
{
- cameraParent.transform.position = vessel.transform.position+vessel.rb_velocity*Time.fixedDeltaTime;
- cameraParent.transform.rotation = vessel.transform.rotation;
+ CTPersistantField.Load("CToolsSettings", typeof(CamTools), this);
+ guiOffsetForward = manualOffsetForward.ToString();
+ guiOffsetRight = manualOffsetRight.ToString();
+ guiOffsetUp = manualOffsetUp.ToString();
+ guiKeyZoomSpeed = keyZoomSpeed.ToString();
+ guiFreeMoveSpeed = freeMoveSpeed.ToString();
- if(isPlayingPath)
+ DeselectKeyframe();
+ availablePaths = new List();
+ ConfigNode pathFileNode = ConfigNode.Load(CameraPath.pathSaveURL);
+ if (pathFileNode == null)
{
- CameraTransformation tf = currentPath.Evaulate(pathTime * currentPath.timeScale);
- flightCamera.transform.localPosition = Vector3.Lerp(flightCamera.transform.localPosition, tf.position, currentPath.lerpRate*Time.fixedDeltaTime);
- flightCamera.transform.localRotation = Quaternion.Slerp(flightCamera.transform.localRotation, tf.rotation, currentPath.lerpRate*Time.fixedDeltaTime);
- zoomExp = Mathf.Lerp(zoomExp, tf.zoom, currentPath.lerpRate*Time.fixedDeltaTime);
+ pathFileNode = ConfigNode.Load(CameraPath.oldPathSaveURL);
}
- else
+ if (pathFileNode != null)
{
- //move
- //mouse panning, moving
- Vector3 forwardLevelAxis = flightCamera.transform.forward;//(Quaternion.AngleAxis(-90, cameraUp) * flightCamera.transform.right).normalized;
- Vector3 rightAxis = flightCamera.transform.right;//(Quaternion.AngleAxis(90, forwardLevelAxis) * cameraUp).normalized;
- if(enableKeypad)
- {
- if(Input.GetKey(fmUpKey))
- {
- flightCamera.transform.position += cameraUp * freeMoveSpeed * Time.fixedDeltaTime;
- }
- else if(Input.GetKey(fmDownKey))
- {
- flightCamera.transform.position -= cameraUp * freeMoveSpeed * Time.fixedDeltaTime;
- }
- if(Input.GetKey(fmForwardKey))
- {
- flightCamera.transform.position += forwardLevelAxis * freeMoveSpeed * Time.fixedDeltaTime;
- }
- else if(Input.GetKey(fmBackKey))
- {
- flightCamera.transform.position -= forwardLevelAxis * freeMoveSpeed * Time.fixedDeltaTime;
- }
- if(Input.GetKey(fmLeftKey))
- {
- flightCamera.transform.position -= flightCamera.transform.right * freeMoveSpeed * Time.fixedDeltaTime;
- }
- else if(Input.GetKey(fmRightKey))
- {
- flightCamera.transform.position += flightCamera.transform.right * freeMoveSpeed * Time.fixedDeltaTime;
- }
-
- //keyZoom
- if(!autoFOV)
- {
- if(Input.GetKey(fmZoomInKey))
- {
- zoomExp = Mathf.Clamp(zoomExp + (keyZoomSpeed * Time.fixedDeltaTime), 1, 8);
- }
- else if(Input.GetKey(fmZoomOutKey))
- {
- zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, 8);
- }
- }
- else
- {
- if(Input.GetKey(fmZoomInKey))
- {
- autoZoomMargin = Mathf.Clamp(autoZoomMargin + (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, 50);
- }
- else if(Input.GetKey(fmZoomOutKey))
- {
- autoZoomMargin = Mathf.Clamp(autoZoomMargin - (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, 50);
- }
- }
- }
-
- if(Input.GetKey(KeyCode.Mouse1) && Input.GetKey(KeyCode.Mouse2))
+ foreach (var node in pathFileNode.GetNode("CAMERAPATHS").GetNodes("CAMERAPATH"))
{
- flightCamera.transform.rotation = Quaternion.AngleAxis(Input.GetAxis("Mouse X") * -1.7f, flightCamera.transform.forward) * flightCamera.transform.rotation;
+ availablePaths.Add(CameraPath.Load(node));
}
- else
- {
- if(Input.GetKey(KeyCode.Mouse1))
- {
- flightCamera.transform.rotation *= Quaternion.AngleAxis(Input.GetAxis("Mouse X") * 1.7f/(zoomExp*zoomExp), Vector3.up); //*(Mathf.Abs(Mouse.delta.x)/7)
- flightCamera.transform.rotation *= Quaternion.AngleAxis(-Input.GetAxis("Mouse Y") * 1.7f/(zoomExp*zoomExp), Vector3.right);
- flightCamera.transform.rotation = Quaternion.LookRotation(flightCamera.transform.forward, flightCamera.transform.up);
- }
- if(Input.GetKey(KeyCode.Mouse2))
+ }
+ else
+ {
+ availablePaths.Add(
+ new CameraPath
{
- flightCamera.transform.position += flightCamera.transform.right * Input.GetAxis("Mouse X") * 2;
- flightCamera.transform.position += forwardLevelAxis * Input.GetAxis("Mouse Y") * 2;
+ pathName = "Example Path",
+ points = new List {
+ new Vector3(13.40305f, -16.60615f, -4.274539f),
+ new Vector3(14.48815f, -13.88801f, -4.26651f),
+ new Vector3(14.48839f, -13.88819f, -4.267331f),
+ new Vector3(15.52922f, -14.25925f, -4.280066f)
+ },
+ positionInterpolationTypes = new List{
+ PositionInterpolationType.CubicSpline,
+ PositionInterpolationType.CubicSpline,
+ PositionInterpolationType.CubicSpline,
+ PositionInterpolationType.CubicSpline
+ },
+ rotations = new List{
+ new Quaternion( 0.5759971f, 0.2491289f, -0.2965982f, -0.7198553f),
+ new Quaternion(-0.6991884f, 0.09197949f, -0.08556388f, 0.7038141f),
+ new Quaternion(-0.6991884f, 0.09197949f, -0.08556388f, 0.7038141f),
+ new Quaternion(-0.6506922f, 0.2786613f, -0.271617f, 0.6520521f)
+ },
+ rotationInterpolationTypes = new List{
+ RotationInterpolationType.Slerp,
+ RotationInterpolationType.Slerp,
+ RotationInterpolationType.Slerp,
+ RotationInterpolationType.Slerp
+ },
+ times = new List { 0f, 1f, 2f, 6f },
+ zooms = new List { 1f, 2.035503f, 3.402367f, 3.402367f },
+ timeScale = 0.29f
}
- }
- flightCamera.transform.position += flightCamera.transform.up * 10 * Input.GetAxis("Mouse ScrollWheel");
-
+ );
}
-
- //zoom
- zoomFactor = Mathf.Exp(zoomExp) / Mathf.Exp(1);
- manualFOV = 60 / zoomFactor;
- updateFOV = (currentFOV != manualFOV);
- if(updateFOV)
+ selectedPathIndex = Math.Min(selectedPathIndex, availablePaths.Count - 1);
+ if (availablePaths.Count > 0 && selectedPathIndex < 0) { selectedPathIndex = 0; }
+ // Set some internal and GUI variables.
+ freeMoveSpeedRaw = Mathf.Log10(freeMoveSpeed);
+ freeMoveSpeedMinRaw = Mathf.Log10(freeMoveSpeedMin);
+ freeMoveSpeedMaxRaw = Mathf.Log10(freeMoveSpeedMax);
+ zoomSpeedRaw = Mathf.Log10(keyZoomSpeed);
+ zoomSpeedMinRaw = Mathf.Log10(keyZoomSpeedMin);
+ zoomSpeedMaxRaw = Mathf.Log10(keyZoomSpeedMax);
+ zoomMaxExp = Mathf.Log(zoomMax) + 1f;
+ signedMaxRelVSqr = Mathf.Abs(maxRelV) * maxRelV;
+ guiOffsetForward = manualOffsetForward.ToString();
+ guiOffsetRight = manualOffsetRight.ToString();
+ guiOffsetUp = manualOffsetUp.ToString();
+ guiKeyZoomSpeed = keyZoomSpeed.ToString();
+ guiFreeMoveSpeed = freeMoveSpeed.ToString();
+ if (inputFields != null)
{
- currentFOV = Mathf.Lerp(currentFOV, manualFOV, 0.1f);
- flightCamera.SetFoV(currentFOV);
- updateFOV = false;
+ if (inputFields.ContainsKey("freeMoveSpeed"))
+ { inputFields["freeMoveSpeed"].UpdateLimits(freeMoveSpeedMin, freeMoveSpeedMax); }
+ if (inputFields.ContainsKey("keyZoomSpeed"))
+ { inputFields["keyZoomSpeed"].UpdateLimits(keyZoomSpeedMin, keyZoomSpeedMax); }
}
+ if (DEBUG) { Debug.Log("[CameraTools]: Verbose debugging enabled."); }
}
-
+ #endregion
}
-
-
-
-
- public enum ReferenceModes {InitialVelocity, Surface, Orbit}
-
- public enum ToolModes {StationaryCamera, DogfightCamera, Pathing};
+ public enum ToolModes { StationaryCamera, DogfightCamera, Pathing };
}
-
diff --git a/CameraTools/CameraKeyframe.cs b/CameraTools/CameraKeyframe.cs
index a1bf8b04..d5d8eb20 100644
--- a/CameraTools/CameraKeyframe.cs
+++ b/CameraTools/CameraKeyframe.cs
@@ -2,19 +2,23 @@
using System.Collections.Generic;
namespace CameraTools
{
- public struct CameraKeyframe
+ public struct CameraKeyframe
{
public Vector3 position;
+ public PositionInterpolationType positionInterpolationType;
public Quaternion rotation;
+ public RotationInterpolationType rotationInterpolationType;
public float zoom;
public float time;
- public CameraKeyframe(Vector3 pos, Quaternion rot, float z, float t)
+ public CameraKeyframe(Vector3 position, Quaternion rotation, float zoom, float time, PositionInterpolationType positionInterpolationType, RotationInterpolationType rotationInterpolationType)
{
- position = pos;
- rotation = rot;
- zoom = z;
- time = t;
+ this.position = position;
+ this.rotation = rotation;
+ this.zoom = zoom;
+ this.time = time;
+ this.positionInterpolationType = positionInterpolationType;
+ this.rotationInterpolationType = rotationInterpolationType;
}
}
diff --git a/CameraTools/CameraPath.cs b/CameraTools/CameraPath.cs
index 21d93910..76afa059 100644
--- a/CameraTools/CameraPath.cs
+++ b/CameraTools/CameraPath.cs
@@ -1,31 +1,28 @@
using System;
+using System.IO;
using System.Collections.Generic;
+using System.Linq;
using UnityEngine;
namespace CameraTools
{
public class CameraPath
{
+ public static string oldPathSaveURL = Path.GetFullPath(Path.Combine(KSPUtil.ApplicationRootPath, "GameData/CameraTools/paths.cfg"));
+ public static string pathSaveURL = Path.GetFullPath(Path.Combine(KSPUtil.ApplicationRootPath, "GameData/CameraTools/PluginData/paths.cfg"));
public string pathName;
- int keyCount = 0;
- public int keyframeCount
- {
- get
- {
- return keyCount;
- }
- private set
- {
- keyCount = value;
- }
- }
+ public int keyframeCount { get { return points.Count; } }
public List points;
+ public List positionInterpolationTypes;
public List rotations;
+ public List rotationInterpolationTypes;
public List times;
public List zooms;
- public float lerpRate = 15;
+ public float secondarySmoothing = 0;
public float timeScale = 1;
+ public bool isGeoSpatial = false; // Stored points are GPS coordinates instead of positions relative to the cameraParent and rotations are global instead of local.
+ // Note: for now, this is just using spline interpolation between GPS points instead of proper geospatial interpolation via https://www.movable-type.co.uk/scripts/latlong.html, maybe I'll add that later.
Vector3Animation pointCurve;
RotationAnimation rotationCurve;
@@ -38,19 +35,33 @@ public CameraPath()
rotations = new List();
times = new List();
zooms = new List();
+ positionInterpolationTypes = new List();
+ rotationInterpolationTypes = new List();
}
public static CameraPath Load(ConfigNode node)
{
CameraPath newPath = new CameraPath();
- newPath.pathName = node.GetValue("pathName");
- newPath.points = ParseVectorList(node.GetValue("points"));
- newPath.rotations = ParseQuaternionList(node.GetValue("rotations"));
- newPath.times = ParseFloatList(node.GetValue("times"));
- newPath.zooms = ParseFloatList(node.GetValue("zooms"));
- newPath.lerpRate = float.Parse(node.GetValue("lerpRate"));
- newPath.timeScale = float.Parse(node.GetValue("timeScale"));
+ if (node.HasValue("pathName")) { newPath.pathName = node.GetValue("pathName"); }
+ if (node.HasValue("points")) { newPath.points = ParseVectorList(node.GetValue("points")); }
+ if (node.HasValue("positionInterpolationTypes")) { newPath.positionInterpolationTypes = ParseEnumTypeList(node.GetValue("positionInterpolationTypes")); }
+ if (node.HasValue("rotations")) { newPath.rotations = ParseQuaternionList(node.GetValue("rotations")); }
+ if (node.HasValue("rotationInterpolationTypes")) { newPath.rotationInterpolationTypes = ParseEnumTypeList(node.GetValue("rotationInterpolationTypes")); }
+ if (node.HasValue("times")) { newPath.times = ParseFloatList(node.GetValue("times")); }
+ if (node.HasValue("zooms")) { newPath.zooms = ParseFloatList(node.GetValue("zooms")); }
+ if (node.HasValue("secondarySmoothing")) { newPath.secondarySmoothing = float.Parse(node.GetValue("secondarySmoothing")); } else { newPath.secondarySmoothing = 0; }
+ if (node.HasValue("timeScale")) { newPath.timeScale = float.Parse(node.GetValue("timeScale")); } else { newPath.timeScale = 1; }
+ if (node.HasValue("isGeoSpatial")) { newPath.isGeoSpatial = bool.Parse(node.GetValue("isGeoSpatial")); } else { newPath.isGeoSpatial = false; }
+
+ if (node.HasValue("lerpRate") && !node.HasValue("secondarySmoothing")) { var lerpRate = float.Parse(node.GetValue("lerpRate")); newPath.secondarySmoothing = Mathf.Round(-50f * Mathf.Log10(lerpRate)) / 100f; } // Deprecated in favour of secondarySmoothing.
+
+ // Ensure there's a consistent number of entries in the path.
+ while (newPath.positionInterpolationTypes.Count < newPath.points.Count) { newPath.positionInterpolationTypes.Add(PositionInterpolationType.CubicSpline); }
+ while (newPath.rotations.Count < newPath.points.Count) { newPath.rotations.Add(Quaternion.identity); }
+ while (newPath.rotationInterpolationTypes.Count < newPath.points.Count) { newPath.rotationInterpolationTypes.Add(RotationInterpolationType.CubicSpline); }
+ while (newPath.times.Count < newPath.points.Count) { newPath.times.Add(newPath.times.Count); }
+ while (newPath.zooms.Count < newPath.points.Count) { newPath.zooms.Add(1); }
newPath.Refresh();
return newPath;
@@ -58,55 +69,54 @@ public static CameraPath Load(ConfigNode node)
public void Save(ConfigNode node)
{
- Debug.Log("Saving path: " + pathName);
+ Debug.Log("[CameraTools]: Saving path: " + pathName);
ConfigNode pathNode = node.AddNode("CAMERAPATH");
pathNode.AddValue("pathName", pathName);
pathNode.AddValue("points", WriteVectorList(points));
+ pathNode.AddValue("positionInterpolationTypes", WriteEnumTypeList(positionInterpolationTypes));
pathNode.AddValue("rotations", WriteQuaternionList(rotations));
+ pathNode.AddValue("rotationInterpolationTypes", WriteEnumTypeList(rotationInterpolationTypes));
pathNode.AddValue("times", WriteFloatList(times));
pathNode.AddValue("zooms", WriteFloatList(zooms));
- pathNode.AddValue("lerpRate", lerpRate);
+ pathNode.AddValue("secondarySmoothing", secondarySmoothing);
pathNode.AddValue("timeScale", timeScale);
+ pathNode.AddValue("isGeoSpatial", isGeoSpatial);
}
public static string WriteVectorList(List list)
{
- string output = string.Empty;
- foreach(var val in list)
- {
- output += ConfigNode.WriteVector(val) + ";";
- }
- return output;
+ return string.Join(";", list.Select(val => ConfigNode.WriteVector(val)));
}
public static string WriteQuaternionList(List list)
{
- string output = string.Empty;
- foreach(var val in list)
- {
- output += ConfigNode.WriteQuaternion(val) + ";";
- }
- return output;
+ return string.Join(";", list.Select(val => ConfigNode.WriteQuaternion(val)));
}
public static string WriteFloatList(List list)
{
- string output = string.Empty;
- foreach(var val in list)
- {
- output += val.ToString() + ";";
- }
- return output;
+ return string.Join(";", list);
+ }
+
+ public static string WriteEnumTypeList(List list) where T : System.Enum
+ {
+ return string.Join(";", list);
}
public static List ParseVectorList(string arrayString)
{
- string[] vectorStrings = arrayString.Split(new char[]{ ';' }, StringSplitOptions.RemoveEmptyEntries);
+ string[] vectorStrings = arrayString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
List vList = new List();
- for(int i = 0; i < vectorStrings.Length; i++)
+ for (int i = 0; i < vectorStrings.Length; i++)
{
- Debug.Log("attempting to parse vector: --" + vectorStrings[i] + "--");
- vList.Add(ConfigNode.ParseVector3(vectorStrings[i]));
+ try
+ {
+ vList.Add(ConfigNode.ParseVector3(vectorStrings[i]));
+ }
+ catch (Exception e)
+ {
+ Debug.LogError("[CameraTools]: Failed to parse vector: --" + vectorStrings[i] + "--, reason: " + e.Message);
+ }
}
return vList;
@@ -114,9 +124,9 @@ public static List ParseVectorList(string arrayString)
public static List ParseQuaternionList(string arrayString)
{
- string[] qStrings = arrayString.Split(new char[]{ ';' }, StringSplitOptions.RemoveEmptyEntries);
+ string[] qStrings = arrayString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
List qList = new List();
- for(int i = 0; i < qStrings.Length; i++)
+ for (int i = 0; i < qStrings.Length; i++)
{
qList.Add(ConfigNode.ParseQuaternion(qStrings[i]));
}
@@ -126,9 +136,9 @@ public static List ParseQuaternionList(string arrayString)
public static List ParseFloatList(string arrayString)
{
- string[] fStrings = arrayString.Split(new char[]{ ';' }, StringSplitOptions.RemoveEmptyEntries);
+ string[] fStrings = arrayString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
List fList = new List();
- for(int i = 0; i < fStrings.Length; i++)
+ for (int i = 0; i < fStrings.Length; i++)
{
fList.Add(float.Parse(fStrings[i]));
}
@@ -136,30 +146,73 @@ public static List ParseFloatList(string arrayString)
return fList;
}
- public void AddTransform(Transform cameraTransform, float zoom, float time)
+ public static List ParseEnumTypeList(string arrayString) where E : struct, System.Enum
{
- points.Add(cameraTransform.localPosition);
- rotations.Add(cameraTransform.localRotation);
+ var iList = new List();
+ if (string.IsNullOrEmpty(arrayString)) return iList;
+ string[] iStrings = arrayString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
+ for (int i = 0; i < iStrings.Length; i++)
+ {
+ E enumType;
+ if (Enum.TryParse(iStrings[i], out enumType))
+ { iList.Add(enumType); }
+ else
+ { iList.Add(default(E)); }
+ }
+
+ return iList;
+ }
+
+ public void AddTransform(Transform cameraTransform, float zoom, ref float time, PositionInterpolationType positionInterpolationType, RotationInterpolationType rotationInterpolationType)
+ {
+ if (isGeoSpatial)
+ {
+ points.Add((Vector3)WorldPosToGeoCoords(cameraTransform.position));
+ rotations.Add(cameraTransform.rotation);
+ }
+ else
+ {
+ points.Add(cameraTransform.localPosition);
+ rotations.Add(cameraTransform.localRotation);
+ }
zooms.Add(zoom);
+ while (times.Contains(time)) time += 1e-3f; // Avoid duplicate times.
times.Add(time);
- keyframeCount = times.Count;
+ positionInterpolationTypes.Add(positionInterpolationType);
+ rotationInterpolationTypes.Add(rotationInterpolationType);
Sort();
UpdateCurves();
}
- public void SetTransform(int index, Transform cameraTransform, float zoom, float time)
+ public void SetTransform(int index, Transform cameraTransform, float zoom, ref float time, PositionInterpolationType positionInterpolationType, RotationInterpolationType rotationInterpolationType)
{
- points[index] = cameraTransform.localPosition;
- rotations[index] = cameraTransform.localRotation;
+ if (isGeoSpatial)
+ {
+ points[index] = (Vector3)WorldPosToGeoCoords(cameraTransform.position);
+ rotations[index] = cameraTransform.rotation;
+ }
+ else
+ {
+ points[index] = cameraTransform.localPosition;
+ rotations[index] = cameraTransform.localRotation;
+ }
zooms[index] = zoom;
+ while (times.Contains(time)) time += 1e-3f; // Avoid duplicate times.
times[index] = time;
+ positionInterpolationTypes[index] = positionInterpolationType;
+ rotationInterpolationTypes[index] = rotationInterpolationType;
Sort();
UpdateCurves();
}
+ Vector3d WorldPosToGeoCoords(Vector3 position)
+ {
+ FlightGlobals.currentMainBody.GetLatLonAlt(position, out double lat, out double lon, out double alt);
+ return new(lat, lon, alt);
+ }
+
public void Refresh()
{
- keyframeCount = times.Count;
Sort();
UpdateCurves();
}
@@ -170,20 +223,24 @@ public void RemoveKeyframe(int index)
rotations.RemoveAt(index);
zooms.RemoveAt(index);
times.RemoveAt(index);
- keyframeCount = times.Count;
+ positionInterpolationTypes.RemoveAt(index);
+ rotationInterpolationTypes.RemoveAt(index);
UpdateCurves();
}
public void Sort()
{
- List keyframes = new List();
- for(int i = 0; i < points.Count; i++)
+ List keyframes = new();
+ List _times = new();
+ for (int i = 0; i < points.Count; i++)
{
- keyframes.Add(new CameraKeyframe(points[i], rotations[i], zooms[i], times[i]));
+ while (_times.Contains(times[i])) times[i] += 1e-3f; // Sanitise times to avoid duplicates.
+ _times.Add(times[i]);
+ keyframes.Add(new CameraKeyframe(points[i], rotations[i], zooms[i], times[i], positionInterpolationTypes[i], rotationInterpolationTypes[i]));
}
keyframes.Sort(new CameraKeyframeComparer());
- for(int i = 0; i < keyframes.Count; i++)
+ for (int i = 0; i < keyframes.Count; i++)
{
points[i] = keyframes[i].position;
rotations[i] = keyframes[i].rotation;
@@ -195,34 +252,31 @@ public void Sort()
public CameraKeyframe GetKeyframe(int index)
{
int i = index;
- return new CameraKeyframe(points[i], rotations[i], zooms[i], times[i]);
+ return new CameraKeyframe(points[i], rotations[i], zooms[i], times[i], positionInterpolationTypes[i], rotationInterpolationTypes[i]);
}
public void UpdateCurves()
{
- pointCurve = new Vector3Animation(points.ToArray(), times.ToArray());
- rotationCurve = new RotationAnimation(rotations.ToArray(), times.ToArray());
+ pointCurve = new Vector3Animation(points.ToArray(), times.ToArray(), positionInterpolationTypes.ToArray());
+ rotationCurve = new RotationAnimation(rotations.ToArray(), times.ToArray(), rotationInterpolationTypes.ToArray());
zoomCurve = new AnimationCurve();
- for(int i = 0; i < zooms.Count; i++)
+ for (int i = 0; i < zooms.Count; i++)
{
zoomCurve.AddKey(new Keyframe(times[i], zooms[i]));
}
}
- public CameraTransformation Evaulate(float time)
+ public CameraTransformation Evaluate(float time)
{
- CameraTransformation tf = new CameraTransformation();
- tf.position = pointCurve.Evaluate(time);
- tf.rotation = rotationCurve.Evaluate(time);
- tf.zoom = zoomCurve.Evaluate(time);
+ CameraTransformation tf = new()
+ {
+ position = pointCurve.Evaluate(time),
+ rotation = rotationCurve.Evaluate(time),
+ zoom = zoomCurve.Evaluate(time)
+ };
return tf;
}
-
-
-
-
-
}
}
diff --git a/CameraTools/CameraTools.csproj b/CameraTools/CameraTools.csproj
index 49969b6e..d76ac411 100644
--- a/CameraTools/CameraTools.csproj
+++ b/CameraTools/CameraTools.csproj
@@ -1,22 +1,14 @@
-
+
Debug
- x86
- 9.0.21022
- 2.0
+ AnyCPU
{446E2470-00DC-4835-B62D-9DB8A7D41F4A}
Library
CameraTools
CameraTools
- v4.7.2
-
-
-
-
- 3.5
-
-
+ v4.8
+ preview
True
@@ -47,14 +39,12 @@
full
AnyCPU
prompt
- MinimumRecommendedRules.ruleset
false
bin\Release\
AnyCPU
prompt
- MinimumRecommendedRules.ruleset
true
false
portable
@@ -62,17 +52,31 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -93,9 +97,6 @@
..\..\_LocalDev\KSPRefs\UnityEngine.AnimationModule.dll
-
- ..\..\_LocalDev\KSPRefs\UnityEngine.AssetBundleModule.dll
-
..\..\_LocalDev\KSPRefs\UnityEngine.AudioModule.dll
@@ -114,12 +115,6 @@
..\..\_LocalDev\KSPRefs\UnityEngine.InputModule.dll
-
- ..\..\_LocalDev\KSPRefs\UnityEngine.JSONSerializeModule.dll
-
-
- ..\..\_LocalDev\KSPRefs\UnityEngine.ParticleSystemModule.dll
-
..\..\_LocalDev\KSPRefs\UnityEngine.PhysicsModule.dll
@@ -139,33 +134,22 @@
..\..\_LocalDev\KSPRefs\UnityEngine.UIModule.dll
-
- ..\..\_LocalDev\KSPRefs\UnityEngine.UnityWebRequestWWWModule.dll
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ @echo $(Targetname)
+ @echo ...
+ @echo set lpath vars from LocalDev storage...
+ set /p KSP_DIR=<"$(ProjectDir)LocalDev\ksp_dir.txt"
+ set /p PDB2MDB_EXE=<"$(ProjectDir)LocalDev\pdb2mdb_exe.txt"
+ set /p ZA_DIR=<"$(ProjectDir)LocalDev\7za_dir.txt"
+ set /p DIST_DIR=<"$(ProjectDir)LocalDev\dist_dir.txt"
+
+ @echo distributing $(Targetname) files...
+ copy /Y "$(TargetPath)" "$(ProjectDir)Distribution\GameData\CameraTools\Plugins\"
+
+ if $(ConfigurationName) == Debug (
+ @echo building $(Targetname).dll.mdb file...
+ cd "$(TargetDir)"
+ copy /Y "$(TargetDir)$(Targetname).pdb" "%25KSP_DIR%25\GameData\CameraTools\Plugins\"
+ )
+
+ @echo packaging files...
+ if exist "%25DIST_DIR%25\CameraTools*.zip" del "%25DIST_DIR%25\CameraTools*.zip"
+ call "%25ZA_DIR%25\7za.exe" a -tzip -r "%25DIST_DIR%25\CameraTools.@(VersionNumber)_%25DATE:~4,2%25%25DATE:~7,2%25%25DATE:~10,4%25.zip" "$(ProjectDir)Distribution\*.*"
+
+ @echo Deploy $(Targetname) Distribution files to test env: %25KSP_DIR%25\GameData...
+ @echo copying:"$(SolutionDir)\CameraTools\Distribution\GameData" to "%25KSP_DIR%25\GameData"
+ xcopy /E /Y "$(SolutionDir)\CameraTools\Distribution\GameData" "%25KSP_DIR%25\GameData"
+
+ if $(ConfigurationName) == Debug (
+ copy /Y "$(TargetDir)$(Targetname).pdb" "%25KSP_DIR%25\GameData\CameraTools\Plugins\"
+ )
+
+ @echo Build/deploy complete!
+
+
+ echo $(Targetname)
+ export ModName=CameraTools
+ echo Copying assemblies to Distribution $(Targetname) files...
+ mkdir -p "$(ProjectDir)/Distribution/GameData/${ModName}/Plugins/"
+ cp -a "$(TargetDir)"CameraTools*.dll "$(ProjectDir)Distribution/GameData/${ModName}/Plugins/"
+ if [ "$(ConfigurationName)" = "Debug" ]
+ then
+ echo building debug files and symbols...
+ cp -a "$(TargetDir)"CameraTools*.pdb "$(ProjectDir)Distribution/GameData/${ModName}/Plugins/"
+ fi
+
+ if [ -e "$(ProjectDir)Distribution/${ModName}".*.zip ]
+ then
+ echo deleting previous build ...
+ rm "$(ProjectDir)Distribution/${ModName}".*.zip
+ fi
+ echo packaging new build...
+ 7za a -tzip -r "$(ProjectDir)Distribution/${ModName}.@(VersionNumber)_`date -u -Iseconds`.zip" "$(ProjectDir)Distribution/*.*"
+
+
+ bash -c 'cat $(ProjectDir)../../_LocalDev/ksp_dir.txt | while read KSP_DIR; do
+ if [[ "${KSP_DIR:0:1}" == "#" ]]; then continue; fi
+ echo Deploy $(ProjectDir) Distribution files to test env: "${KSP_DIR}/GameData"...
+ echo copying:"$(ProjectDir)Distribution/GameData" to "${KSP_DIR}/GameData"
+ cp -a "$(ProjectDir)Distribution/GameData/${ModName}" "${KSP_DIR}/GameData"
+ done'
+
+ echo Build/deploy complete!
+
+
+
+
+
+
+
+
+
+
+
+
+ $(PostBuildEventDependsOn);
+ PostBuildMacros;
+
+
+
+
+
\ No newline at end of file
diff --git a/CameraTools/Curve3D.cs b/CameraTools/Curve3D.cs
index 935f0c64..dd205a02 100644
--- a/CameraTools/Curve3D.cs
+++ b/CameraTools/Curve3D.cs
@@ -26,7 +26,7 @@ public void SetPoints(Vector3[] newPoints, float[] newTimes)
{
if(newPoints.Length != newTimes.Length)
{
- Debug.LogError("Curve3D: points array must be same length as times array");
+ Debug.LogError("[CameraTools]: Curve3D: points array must be same length as times array");
return;
}
points = new Vector3[newPoints.Length];
@@ -48,7 +48,7 @@ public void SetPoint(int index, Vector3 newPoint, float newTime)
}
else
{
- Debug.LogError("Tried to set new point in a Curve3D beyond the existing array. Not yet implemented.");
+ Debug.LogError("[CameraTools]: Tried to set new point in a Curve3D beyond the existing array. Not yet implemented.");
}
}
@@ -92,7 +92,7 @@ public Vector3 GetPoint(float time)
{
if(!curveReady)
{
- Debug.LogWarning("Curve was accessed but it was not properly initialized.");
+ Debug.LogWarning("[CameraTools]: Curve was accessed but it was not properly initialized.");
return Vector3.zero;
}
@@ -106,7 +106,7 @@ public Vector3 GetTangent(float time)
{
if(!curveReady)
{
- Debug.LogWarning("Curve was accessed but it was not properly initialized.");
+ Debug.LogWarning("[CameraTools]: Curve was accessed but it was not properly initialized.");
return Vector3.one;
}
diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version
index 13ea1933..bcf99cdb 100644
--- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version
+++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version
@@ -1,18 +1,18 @@
{
"NAME": "CameraTools",
- "URL": "https://raw.githubusercontent.com/jrodrigv/CameraTools/master/CameraTools/Distribution/GameData/CameraTools/CameraTools.version",
- "DOWNLOAD": "https://github.com/jrodrigv/CameraTools/releases/tag/v1.14.0",
- "CHANGE_LOG_URL": "https://github.com/jrodrigv/CameraTools/blob/master/CameraTools/Distribution/GameData/CameraTools/Changelog.txt",
+ "URL": "https://raw.githubusercontent.com/BrettRyland/CameraTools/master/CameraTools/Distribution/GameData/CameraTools/CameraTools.version",
+ "DOWNLOAD": "https://github.com/BrettRyland/CameraTools",
+ "CHANGE_LOG_URL": "https://github.com/BrettRyland/CameraTools/blob/master/CameraTools/Distribution/GameData/CameraTools/Changelog.txt",
"VERSION": {
"MAJOR": 1,
- "MINOR": 14,
- "PATCH": 0,
+ "MINOR": 37,
+ "PATCH": 1,
"BUILD": 0
},
"KSP_VERSION": {
"MAJOR": 1,
- "MINOR": 9,
- "PATCH": 0
+ "MINOR": 12,
+ "PATCH": 5
},
"KSP_VERSION_MIN": {
"MAJOR": 1,
@@ -21,7 +21,7 @@
},
"KSP_VERSION_MAX": {
"MAJOR": 1,
- "MINOR": 9,
+ "MINOR": 12,
"PATCH": 99
}
}
diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt
index 60b71ed2..41c40153 100644
--- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt
+++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt
@@ -1,3 +1,350 @@
+v1.37.1
+Improvements / Bugfixes:
+- Compatibility fixes with pre-KSP v1.12 versions.
+- Include the UI scaling for the tooltip position (can't adjust size though).
+- Minor layout tweaks to account for strings being clipped for some UI scaling values.
+
+v1.37.0
+Improvements / Bugfixes:
+- Respect random mode when auto-starting for BDArmory.
+- Account for vessel size in choice of random mode.
+- Adjust autoFlyBy/randomMode minimum offsets in stationary camera to account for vessel size.
+- Revert to pivoting around the camera in stationary mode if in target-pivot mode and there's no target to pivot around.
+- Reset lerp momentum for the dogfight inertial chase mode when starting the dogfight camera (reduces issues from Kraken events).
+
+v1.36.2
+Improvements / Bugfixes:
+- Use the vessel's reference transform in case the default transform is not the correct one.
+
+v1.36.1
+Improvements / Bugfixes:
+- Add in a missing Krakensbane correction for the dogfight inertial chase camera rotation target.
+
+v1.36.0
+Improvements / Bugfixes:
+- Add a UI scaling option that is tied to the global UI_SCALE setting by default.
+
+v1.35.0
+Improvements / Bugfixes:
+- Add better handling of dogfight mode for BDA waypoint mode with combat.
+- Adjust get/set accessor templates to return specified types instead of generic objects.
+- Reduce / eliminate unintended roll and camera offset in dogfight mode due to low lerp rates.
+- Add a slider for selecting the reference frame to use for the dogfight offset.
+- Adjust the dogfight inertial chase slider to the range 0—1 and adjust the "inertia" behaviour, including affecting camera rotation.
+- Add a chase plane mode to dogfight mode where the secondary target is the active plane and roll is disabled. Allow choosing which part (or CoM) to track.
+- Add controller input (AXIS_CAMERA_HDG and AXIS_CAMERA_PITCH) to freelook in dogfight mode and camera rotation in stationary and pathing modes.
+- Add a geospatial pathing option to pathing mode where points are GPS coordinates and rotations are absolute instead of relative to the active vessel.
+ - It can be a bit jittery when Krakensbane is active and the floating origin is more than a few km away.
+ - Also, for now, this is just using spline interpolation between GPS points instead of proper geospatial interpolation via https://www.movable-type.co.uk/scripts/latlong.html, so paths crossing 180° longitude won't work properly.
+
+v1.34.0
+Improvements / Bugfixes:
+- Chinese localisation thanks to user ThreeMACH.
+- Pivot toggle:
+ - Add an option to toggle pivot points between the camera and the target (if valid) when rotating with the right mouse button.
+ - A hotkey can be assigned to this toggle, but is not assigned by default.
+ - When pivoting around the camera, the behaviour is unchanged.
+ - When pivoting around a target in pathing or stationary camera modes, the behaviour is similar to KSP's standard camera movement depending on whether the movement modifier is being held or not.
+ - While the movement modifier is not being held, rotation is aligned with the camera's axes.
+ - While the movement modifier is being held, rotation is aligned with the target vessel's axes.
+ - In pathing camera mode, the pivot point is the vessel's reference transform position.
+ - In stationary camera mode, the pivot point is either the targeted part or the vessel CoM if that option is selected.
+ - In dogfight camera mode, the pivot toggle affects the free-look mode and pivots around the closest point to the vessel along the camera's forward axis.
+- Fix camera roll when transitioning between modified and non-modified movement modes in stationary camera mode.
+- The free-move mode (position vs speed) and pivot mode are now preserved in the settings.
+- Tweak the mouse sensitivity for input to rotations for more comfortable rotations.
+
+v1.33.0
+Improvements / Bugfixes:
+- Add a slider for controlling how long after BDA's secondary dogfight target dies (for non-missiles) before selecting a new secondary non-missile target (to enable lingering on kills).
+- Fix false positives of Krakensbane being active on vessel switches.
+
+v1.32.0
+Improvements / Bugfixes:
+- Improvements to death camera angles and transitions for dogfight mode.
+- Add a button to switch from pathing mode to stationary mode while maintaining the current view.
+- Adjust location of random mode options.
+- Add a button for resetting the camera roll (in stationary and pathing modes).
+- Optimisations to use vessel.CoM instead of vessel.transform.position where applicable.
+- Apply maxRelV to the stationary camera when using random mode.
+- Remove the low-altitude stationary mode override for random mode.
+- Prevent the camera from going below terrain and water (when vessel is only partially submerged) in dogfight mode.
+- Separate the auto-zoom toggle between dogfight and stationary modes.
+- Adjust default settings for a dynamic dogfight mode.
+- Add a slider for setting the minimum interval to check for a new secondary dogfight target if the secondary target is a missile.
+- Don't restart the dogfight camera when switching secondary targets for smoother transitions.
+- Krakensbane optimisations (from BDArmory).
+- Finish adding English (en-us) localisation and tooltips.
+
+v1.31.0
+Improvements / Bugfixes:
+- Fix NREs from atmospheric audiosources when not in atmosphere.
+- Don't reset the original camera parent's position when reverting (fixes weird camera angle).
+- Override the camera restore distance with custom distance (from BDA) when reverting due to auto-enabling for BDA.
+- Add support for a new BDA helper property for inhibiting camera tools.
+- Add support for new BDA helpers for tracking missiles' targets in dogfight mode.
+- Minor tweaks to MouseAimFlight helpers.
+- Some internal restructuring of utils.
+- Begin adding localisation support (not yet complete).
+- Begin adding tooltips (not yet complete).
+
+v1.30.1
+Improvements / Bugfixes:
+- Sanitise timestamps by shifting duplicates by 0.001s instead of breaking the view frustrum with NaNs and avoid division by 0 in spline calculations.
+
+v1.30.0
+Improvements / Bugfixes:
+- Fix stock aero FX not being applied and provide a toggle to disable them.
+- Adjustments to fix some issues when BDArmory should be inhibiting CameraTools and more detailed debugging messages.
+- Add check for new BDA OrbitalAI.
+
+v1.29.2
+Improvements / Bugfixes:
+- Fix an AudioSource memory leak.
+
+v1.29.1
+Improvements / Bugfixes:
+- Fix wind-tear audio being overly loud from recent tweaks.
+
+v1.29.0
+Improvements / Bugfixes:
+- Add a configurable movement threshold to activating free-look mode (default: 0.1).
+- If the camera parent gets stolen, automatically steal it back if it's the original camera parent (due to spawning a kerbal, BDA MML, etc.).
+- Allow customising the maximum value of the zoom, auto zoom, dogfight distance and dogfight offsets in the settings.cfg.
+- Optimisations to how part audio effects are managed.
+- Rework the sonic boom calculations, allowing them to reset and removing the booming when switching vessels/restarting camera modes.
+
+v1.28.0
+Improvements / Bugfixes:
+- Adjust random mode selection for low altitude to be more amenable to BDA pod-racing.
+- Add a visual toggle for free-move mode (speed vs position).
+- Add an optional keybind for resetting the roll in stationary camera mode. Initially unbound, remove the entry from the settings.cfg to unbind it once bound.
+- Fix camera position in Stationary and Dogfight modes when returning from Map mode.
+- Add a free-look mode to Dogfight mode (hold right mouse button, compatible with MouseAimFlight integration).
+
+v1.27.0
+Improvements / Bugfixes:
+- Fix dogfight centroid mode.
+- Add checks for the active vessel being a BDA missile and use dogfight chase mode if it is.
+- Add roll option to stationary camera using right+middle mouse buttons, with the movement modifier key (keypad enter) switching between camera and world coordinate systems.
+- Add support for using the MouseAimFlight mod in dogfight camera mode.
+- Delay camera activation/deactivation when not in flight mode until back in flight mode.
+
+v1.26.0
+Improvements / Bugfixes:
+- Prevent the camera from going below the surface in dogfight mode when the vessel is landed or near the surface when splashed.
+- Disable roll and offset in dogfight mode when the vessel is an EVA kerbal.
+- Ignore ksp_dir.txt lines starting with # for post-build events in Linux (not sure how Windows should do this).
+- Fix bug in field name for input boxes.
+
+v1.25.0
+Improvements / Bugfixes:
+- Add an inertial chase mode to the dogfight camera.
+ - Inertial factor provides a looseness to the camera position.
+ - Offsets are relative to the vessel frame of reference, instead of the camera frame of reference.
+- Save / restore other AudioSource fields that get modified as part of doppler SFX in case this is part of the cause of some sound bugs (not sure if it is or not, but better safe than sorry).
+- Rework the BDA secondary target priority to favour the vessel's target (instead of attacker) when it has recently been firing or fired a missile (requires BDA+ v1.5.4.1 or later).
+- Add a slider to control the minimum update interval of the BDA secondary target selection after switching targets (updates occur every 0.5s after the minimum interval has elapsed).
+
+v1.24.0
+Improvements / Bugfixes:
+- Initialise the "maxRelV" numeric field properly so that the field-width parameter is 6, not the min value.
+- Cache the atmospheric audio sound clips to avoid GC allocations.
+- Include the fix (from BDArmory) for Apple Silicon (M1 chip) not calculating sqrt properly when multiplied by a float.
+ - https://issuetracker.unity3d.com/issues/m1-incorrect-calculation-of-values-using-multiplication-with-mathf-dot-sqrt-when-an-unused-variable-is-declared
+- Add an auto-landing option to the stationary camera.
+ - Without "Maintain Vel." enabled, the position of the camera is based on the vessel's current position.
+ - With "Maintain Vel." enabled, the position of the camera is based on the vessel's predicted terrain intercept if it follows a ballistic trajectory (no drag).
+ - The altitude of the camera above the terrain is defined by the "Up" component of the "Manual Flyby Position".
+ - An extra horizontal offset is defined by the "Fwd" (in the vessel's velocity direction when activated) and "Right" components.
+
+v1.23.0
+Improvements / Bugfixes:
+- Fix some memory leaks detected by KSPCF.
+- Refactor integration with other mods into their own files (mostly). Some BDArmory-related settings may need resetting.
+- Allow deploying to multiple KSP instances when compiling in Linux.
+- Add speed free-move mode for keyboard input (default toggle [2]).
+ - Toggling this resets the speed to zero.
+ - Disabled when in numeric input mode.
+- Update numeric input fields when making changes with keyboard input.
+- Add display field-width parameter to numeric input fields.
+
+v1.22.0
+Bugfixes:
+- Add check for the class type of VesselSpawner due to the current changes in BDA dev.
+- Fix BindingFlags for initial reflection due to changes in BDArmory.
+Improvements:
+- Add a target centroid option for the dogfight mode.
+- Replace reflection for BDArmory with delegates for much faster field/property access.
+- Lower the log error for not being able to set IVA camera mode to a warning.
+
+v1.21.0
+Improvements:
+- Updated fields/properties for an internal refactor in BDArmory.
+- Don't revert the camera when there's no further dogfight targets.
+
+v1.20.0
+Bugfixes:
+- Don't reset the zoom value when reverting the FoV.
+- Fix the lower limit of the camera shake multiplier when using numeric fields.
+- Make the config paths relative to the KSP app location (makes it more relocatable).
+- Fix an NRE in the audio controller.
+Improvements:
+- Add a version number and activation toggle/indicator to the GUI.
+- Separate zoom and autozoom parameters for the different modes so that adjusting zoom levels in one mode doesn't affect other modes.
+- Tweak the camera shake slider to use steps of 0.1.
+- Move the floating origin corrections for the stationary camera to the BetterLateThanNever timing phase to avoid the occasional flicker.
+- Remove the 0 minimum of the max relative velocity to allow reverse fly-bys with the stationary camera again.
+- Only disable TimeControl's CameraZoomFix while CameraTools is active so as to avoid interfering with that mod while CameraTools isn't active.
+- Look for and disable BetterTimeWarp's ScaleCameraSpeed while CameraTools is active, since that also messes with CameraTools during slow-mo.
+- BDA Auto Targeting: add an option to not target incoming missiles.
+- Corrections to the KrakensbaneWarpCorrection for dogfight and stationary camera modes so that they work (almost) correctly at all altitudes and warp levels.
+ - Known issues for the stationary camera when maintaining orbital velocity are:
+ - When changing low warp rate at above 100km altitude a slow drift begins, as if the orbit calculation position is slightly wrong. I.e., starting at a given low warp and staying there is fine, but once changed the drift begins.
+ - Below 100km altitude, there is a small unsteady drift when not in high warp (and exaggerated by low warp) and once noticeably present continues after entering high warp.
+ - Switching in and out of map mode returns the camera to the wrong position.
+
+v1.19.2
+Bugfixes:
+- Check for the flight camera being null on startup (caused by other modules crashing KSP).
+- Fix check for secondary camera target priorities (incoming missiles are prioritised, then incoming fire, then current vessel's target).
+- Ignore maxRelV when random mode is enabled (should fix badly placed stationary camera on vessel switches).
+Improvements:
+- Allow the stationary camera to be placed up to 5km higher to avoid potential terrain line-of-sight issues.
+
+v1.19.1
+Bugfixes:
+ - Fix random mode transitions that got broken in the last release.
+ - Delay activating the new camera mode on vessel switches until LateUpdate for all camera modes, not just dogfight mode.
+ - Reset the zoom slider when resetting the FOV.
+ - Auto-switch the auto-zoom to regular zoom when entering pathing mode as pathing only uses the regular zoom.
+ - Remove the temporaryRevert code (I think that was a Scott Manley hack).
+Improvements:
+ - Adds autoEnableOverride for BDArmory's tournament inter-round time-warping.
+
+v1.19.0
+Improvements:
+ - Use a more stable direction for dogfight mode when the vessel is bobbing around on the ocean.
+ - Auto-Enable for BDArmory when using surface AIs too.
+ - Interpolation rate setting renamed to 'Secondary Smoothing', which takes values in the linear range from 0 to 1 to try to give the user a more intuitive understanding of what the setting does. Actual Lerp values are calculated as 10 ^ {-2 * secondary smoothing} (i.e., 1 to 0.01). Old paths are automatically upgraded to use the new variable.
+ - The default hotkey for toggling the menu is now [0] (keypad 0) to avoid a conflict with the new AI GUI in BDArmory, which uses [/].
+ - A variety of QoL improvements for pathing mode by HatBat:
+ - Keys created during path playback are now automatically inserted at the current path time. Useful for molding paths in to specific shapes.
+ - Added a 'Maintain Speed' button to the keyframe editor that adjusts the duration of the current keyframe to approximately match the speed between the previous two keyframes. Useful for smoothly extending paths and correcting speed inconsistencies in complex paths.
+ - Camera updates can now be switched between Update or 'Real-time' and FixedUpdate or 'In-Game Time'. 'Real-time' creates smoother paths and works while the game is paused, which helps to reduce lag, while 'In-Game Time' is useful for making sure pathing speed is consistent with game physics and that none of the path is skipped when processing footage to remove lag. See FFmpeg's mpdecimate.
+ - New keyframes created while CameraTools is inactive are now correctly started at the current camera position, rather than on top of the vessel facing down or inside the vessel, and use the stock zoom.
+ - Path playback now starts from the selected keyframe if one is selected. Useful for previewing small changes to long paths.
+ - The flight camera now correctly reverts to its original distance, orientation, zoom state and mode when pathing is deactivated.
+ - Camera zoom is now updated immediately rather than gradually when selecting a keyframe or starting a path.
+ - [7]/[1] now moves the pathing camera up/down relative to the frame instead of the ground. This functionality was previously on the scroll wheel.
+ - Vertical movement with the mouse is now triggered by holding both the middle and left mouse buttons and moving the mouse forward/backward (replaces the previous scroll wheel functionality).
+ - Holding keypad Enter (configurable) modifies the reference frame for movement to be relative to the vessel instead of the camera. Also, left/right and forward/backward are reversed for more natural movement when viewing the vessel from the front, which is the typical use-case.
+ - The scroll wheel now adjusts the move speed while the pathing camera is active. This is useful for quickly switching between fine adjustments and repositioning of the camera.
+ - The default time increment for new keys has been changed back to 10 seconds from 1 second. This is a more practical default path length and makes manually inserting new keys easier.
+ - The default rotation interpolation mode is now CubicSpline, was previously Slerp.
+ - The last path you used is now remembered between restarts.
+ - The GUI no longer closes on pressing save. Replaced by a bit of visual feedback. Useful for habitual save pressers.
+
+v1.18.2:
+Improvements:
+ - Re-add the "interpolation rate" parameter in pathing. (A value of 1 means that the camera follows the defined path precisely. A value less than 1 means that the camera chases the point on the path.)
+
+v1.18.1:
+Improvements:
+ - Add customisable min/max values to the keypad move and zoom speeds (editable in the settings.cfg).
+
+v1.18.0:
+Improvements:
+ - Add option for text input fields instead of sliders.
+ - Add 'Maintain Vel.' option, which is the equivalent of the old 'Initial Velocity' reference mode.
+ - Make the menu toggle hotkey configurable too.
+ - Make loading of paths a bit more fault tolerant.
+
+v1.17.0
+Bugfixes:
+ - Only activate the camera when changing modes if it was already active.
+ - Don't automatically start playing a path when adjusting it or creating a new one and stop it if keyframes or the path is deleted.
+ - Start playing a path when it's loaded if camera tools is active.
+ - Adjust ordering of stationary path updates to avoid jitter.
+Improvements:
+ - Automatically disable TimeControl's camera zoom fix as it breaks CameraTools when slow-mo is enabled (sorry @ntwest).
+ - Switch stationary and pathing cameras to run in Update instead of FixedUpdate as they aren't sensitive to physics updates.
+ - Switch pathing camera to use unscaled time so that it works while paused.
+ - Add option of cubic spline interpolation to pathing mode positions based on https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Interpolation_on_an_arbitrary_interval.
+ - Add option of Slerp and cubic spline interpolation to pathing mode rotations.
+ - Note: the interpolation method is used from the keypoint it's set in until the next one, so different parts of the path can use different interpolation methods.
+ - Rework stationary camera to actually make the camera stationary when below maxRelV. Jumps due to CoM changes (e.g., launch clamp detachment) should not affect the camera position unless maxRelV is extremely low (e.g., <1 in most cases).
+ - Option to save the previously used rotation in stationary camera mode when no target is selected (prevents semi-random initial views, default: disabled).
+ - Add keybinding options for free move keys (escape cancels keybinding).
+
+v1.16.4
+ - Recompiled with KSP 1.12 assemblies.
+
+v1.16.3
+Bugfixes:
+ - Fix potential NRE when enabling IVA mode.
+ - Abort trying to enter IVA mode if it throws an exception.
+ - Don't add atmospheric controllers to objects that already have one.
+
+v1.16.2
+Bugfixes:
+ - Respect random mode when manually activating the camera.
+ - Exit IVA mode more reliably to avoid broken flickering camera.
+ - Performance optimisations to avoid GC allocations.
+
+v1.16.1
+Bugfixes:
+ - Fix NRE when vessel is destroyed before switching to IVA mode.
+ - Don't immediately re-enter IVA mode if manually switched out of it.
+
+v1.16.0
+Bugfixes:
+ - Try to reacquire a dogfight target if reverted due to a null dogfight target and Auto-Enable for BDArmory is enabled.
+ - Limit the type of vessels atmospheric audio gets added to to avoid saturating KSP's SoundManager channels unnecessarily.
+ - Fix indexing bug in 'pathing mode' (note: the example path is still weird).
+Improvements:
+ - Add sliders for configuring chances of 'random mode'.
+ - Add IVA view to 'random mode' (if the vessel has a cockpit, otherwise it counts as dogfight mode).
+ - In 'random mode', use the stationary camera when switching to views of a target that is close to the ground or about to crash.
+ - Improve the positioning of the stationary camera when switching to views of a target that is close to the ground or about to crash.
+ - Recover better from something else stealing the camera parent.
+
+v1.15.4
+Bugfixes:
+ - Exclude music volume levels from volumes based on distance from the camera.
+Improvements:
+ - Migrate non-static config files to PluginData to avoid invalidating the ModuleManager cache (should give slightly faster start-up times).
+
+v1.15.3
+Bugfixes:
+ - Disable slider discretisation when keypad control is enabled.
+ - Properly correct for time-warp under almost all conditions for dogfight mode. (It only gets the offset wrong between 70km and 100km above Kerbin and similarly on other bodies.)
+ - Eliminate pretty much all graphical glitches caused by floating origin and Krakensbane velocity shifts in dogfight mode.
+Improvements:
+ - Change the keypad move/zoom speeds to sliders for easier access.
+ - Add option to turn on lots of debugging.
+
+v1.15.2
+Bugfixes:
+ - Don't touch the camera when we're not in control of it!
+ - Use TimeWarp times instead of Time times for velocity calculations.
+Improvements:
+ - Allow switching into and out of IVA and Map modes.
+
+v1.15.1
+Bugfixes:
+ - Fix missing config file load/save bug.
+ - Update all version numbers properly.
+
+v1.15.0
+- A bunch of bugfixes.
+- Save settings properly.
+- Death camera: when in dogfight mode, temporarily follow the explosion when the followed plane gets destroyed.
+- Adjustable lerp (interpolation) rate for dogfight mode.
+- Adjustable roll amount (based on the followed craft) for dogfight mode.
+- Smoother transitions into dogfight mode.
+- Auto-enable for BDArmory.
+
v1.14.0
- Compatibility with KSP 1.9.x
@@ -21,7 +368,6 @@ v1.8.0
- Compatibility with KSP 1.2.2 and KSP 1.2.9 BETA
- Fixing isssue when reverting the camera
-
v1.7.0
- Compatibility with KSP 1.2.1
@@ -74,4 +420,4 @@ v1.1
v1.0
-- Initial release
\ No newline at end of file
+- Initial release
diff --git a/CameraTools/Distribution/GameData/CameraTools/Localization/en-us.cfg b/CameraTools/Distribution/GameData/CameraTools/Localization/en-us.cfg
new file mode 100644
index 00000000..6792c304
--- /dev/null
+++ b/CameraTools/Distribution/GameData/CameraTools/Localization/en-us.cfg
@@ -0,0 +1,172 @@
+Localization
+{
+ // Notes:
+ // - Strings ending in ":" get a space appended to them in StringUtils.Localize so that Localize(field, value) gives "field: value".
+ // - Localization strings ending in ".tip" contain the tooltip for the localization.
+ // - Tooltips can be split over multiple lines using "\n" characters.
+ en-us
+ {
+ #LOC_CameraTools_Title = Camera Tools
+ #LOC_CameraTools_Version = Version:
+ #LOC_CameraTools_Tool = Tool:
+ #LOC_CameraTools_NextMode = >
+ #LOC_CameraTools_NextMode.tip = Next camera mode.
+ #LOC_CameraTools_PrevMode = <
+ #LOC_CameraTools_PrevMode.tip = Prev camera mode.
+ #LOC_CameraTools_ShowTooltips = ?
+ #LOC_CameraTools_ShowTooltips.tip = Toggle tooltips.
+ #LOC_CameraTools_ToggleTextFields = #
+ #LOC_CameraTools_ToggleTextFields.tip = Toggle text fields.
+
+ // General options / Generic
+ #LOC_CameraTools_AudioEffects = Enable Audio Effects
+ #LOC_CameraTools_AudioEffects.tip = Enable atmospheric audio effects such as wind and sonic booms.
+ #LOC_CameraTools_VisualEffects = Enable Visual Effects
+ #LOC_CameraTools_VisualEffects.tip = Enable stock aero / reentry visual effects.
+ #LOC_CameraTools_AutoEnableForBDA = Auto-Enable for BDArmory
+ #LOC_CameraTools_AutoEnableForBDA.tip = Automatically enable CameraTools when BDA starts competitions.
+ #LOC_CameraTools_Autozoom = Auto Zoom
+ #LOC_CameraTools_Autozoom.tip = Automatically adjust the zoom.
+ #LOC_CameraTools_AutozoomMargin = Autozoom Margin:
+ #LOC_CameraTools_AutozoomMargin.tip = How much to automatically adjust the zoom.
+ #LOC_CameraTools_CameraShake = Camera Shake:
+ #LOC_CameraTools_CameraShake.tip = How much to shake the camera due to nearby vessels.
+ #LOC_CameraTools_RandomMode = Random Mode
+ #LOC_CameraTools_RandomMode.tip = Randomly pick a new mode when switching vessels.
+ #LOC_CameraTools_Save = Save
+ #LOC_CameraTools_Save.tip = Save settings to GameData/CameraTools/PluginData/settings.cfg.
+ #LOC_CameraTools_Saving = Saving...
+ #LOC_CameraTools_Saved = Saved.
+ #LOC_CameraTools_Reload = Reload
+ #LOC_CameraTools_Reload.tip = Reload settings from GameData/CameraTools/PluginData/settings.cfg.
+ #LOC_CameraTools_None = None
+ #LOC_CameraTools_Clear = Clear
+ #LOC_CameraTools_Delete = Delete
+ #LOC_CameraTools_Apply = Apply
+ #LOC_CameraTools_Cancel = Cancel
+ #LOC_CameraTools_UIScale = UI Scale:
+ #LOC_CameraTools_UIScaleFollowsStock = Follows Stock
+
+ // Keypad control / Key binding
+ #LOC_CameraTools_KeypadControl = Keypad Control
+ #LOC_CameraTools_KeypadControl.tip = Toggle control of the camera via keybindings\n(on the keypad by default).
+ #LOC_CameraTools_MoveSpeed = Move Speed
+ #LOC_CameraTools_ZoomSpeed = Zoom Speed
+ #LOC_CameraTools_ControlMode = Control Mode
+ #LOC_CameraTools_ControlMode.tip = Toggle between position-based movement\nand speed-based movement.
+ #LOC_CameraTools_PivotMode = Pivot Mode
+ #LOC_CameraTools_PivotMode.tip = Toggle pivot point between the camera position\nand the target's position (when valid).
+ #LOC_CameraTools_EditKeybindings = Edit Keybindings
+ #LOC_CameraTools_Activate = Activate
+ #LOC_CameraTools_Revert = Revert
+ #LOC_CameraTools_Menu = Menu
+ #LOC_CameraTools_Up = Up
+ #LOC_CameraTools_Down = Down
+ #LOC_CameraTools_Forward = Forward
+ #LOC_CameraTools_Back = Back
+ #LOC_CameraTools_Left = Left
+ #LOC_CameraTools_Right = Right
+ #LOC_CameraTools_ZoomIn = Zoom In
+ #LOC_CameraTools_ZoomOut = Zoom Out
+ #LOC_CameraTools_Modifier = Modifier
+ #LOC_CameraTools_FreeMoveMode = FM Mode
+ #LOC_CameraTools_ResetRoll = Reset Roll
+ #LOC_CameraTools_ResetRoll.tip = Reset the camera roll to the local up direction.
+ #LOC_CameraTools_BindKey = Bind Key
+ #LOC_CameraTools_PressAKey = Press A Key
+
+ // Stationary camera
+ #LOC_CameraTools_MaxRelVel = Max Relative Vel.:
+ #LOC_CameraTools_MaxRelVel.tip = The maximum allowed velocity of the camera relative to the active vessel.
+ #LOC_CameraTools_MaintainVel = Maintain Vel.
+ #LOC_CameraTools_MaintainVel.tip = Maintain the velocity that the camera started with.
+ #LOC_CameraTools_UseOrbital = Use Orbital
+ #LOC_CameraTools_UseOrbital.tip = Use orbital velocity instead of world-space velocity when maintaining velocity.
+ #LOC_CameraTools_CameraPosition = Camera Position:
+ #LOC_CameraTools_SetPositionClick = Set Position w/ Click
+ #LOC_CameraTools_ClearPosition = Clear Position
+ #LOC_CameraTools_Waiting = Waiting...
+ #LOC_CameraTools_AutoFlybyPos = Auto Flyby Position
+ #LOC_CameraTools_AutoFlybyPos.tip = Automatically pick a position for a fly-by shot.
+ #LOC_CameraTools_AutoLandingPos = Auto Landing Position
+ #LOC_CameraTools_AutoLandingPos.tip = Automatically pick a position for a nice landing shot.
+ #LOC_CameraTools_ManualFlybyPos = Manual Flyby Position
+ #LOC_CameraTools_ManualFlybyPos.tip = Use a manually specified offset for a fly-by shot.
+ #LOC_CameraTools_FwdPos = Fwd:
+ #LOC_CameraTools_RightPos = Right:
+ #LOC_CameraTools_UpPos = Up:
+ #LOC_CameraTools_CameraTarget = Camera Target:
+ #LOC_CameraTools_SetTargetClick = Set Target w/ Click
+ #LOC_CameraTools_TargetSelf = Target Self
+ #LOC_CameraTools_ClearTarget = Clear Target
+ #LOC_CameraTools_VesselCoM = Target Vessel Center of Mass
+ #LOC_CameraTools_SaveRotation = Save Rotation
+ #LOC_CameraTools_SaveRotation.tip = Save the rotation of the camera when reverting for\nre-use next time the stationary camera is activated.
+
+ // Dogfight camera
+ #LOC_CameraTools_SecondaryTarget = Secondary Target:
+ #LOC_CameraTools_Centroid = Centroid
+ #LOC_CameraTools_BDAAutoTarget = BDA AI Auto Target
+ #LOC_CameraTools_BDAAutoTarget.tip = Let BDArmory decide what the secondary target should be.
+ #LOC_CameraTools_MinimumInterval = Minimum Interval
+ #LOC_CameraTools_MinimumInterval.tip = Minimum interval before letting BDArmory pick a\nnew secondary target (unless the target dies).
+ #LOC_CameraTools_SecondaryTargetDeathSwitchDelay = Target Death Delay
+ #LOC_CameraTools_SecondaryTargetDeathSwitchDelay.tip = Delay before switching secondary targets\nafter a non-missile secondary target dies.
+ #LOC_CameraTools_TargetIncomingMissiles = Target Incoming Missiles
+ #LOC_CameraTools_TargetIncomingMissiles.tip = Prioritize incoming missiles as secondary targets.
+ #LOC_CameraTools_MinimumIntervalMissiles = Min Missile Interval
+ #LOC_CameraTools_MinimumIntervalMissiles.tip = Minimum interval for missile secondary target switching\n(unless the target dies).
+ #LOC_CameraTools_TargetDogfightCentroid = Target Dogfight Centroid
+ #LOC_CameraTools_TargetDogfightCentroid.tip = Use the dogfight centroid as the secondary\ntarget instead of individual vessels.
+ #LOC_CameraTools_Distance = Distance:
+ #LOC_CameraTools_Offset = Offset:
+ #LOC_CameraTools_X = X:
+ #LOC_CameraTools_Y = Y:
+ #LOC_CameraTools_Lerp = Lerp:
+ #LOC_CameraTools_Lerp.tip = How fast the camera follows the target vessel.
+ #LOC_CameraTools_Roll = Roll:
+ #LOC_CameraTools_Roll.tip = How much to tilt the camera based on the target vessel's roll.
+ #LOC_CameraTools_FreeLookThr = Free-Look Thr.:
+ #LOC_CameraTools_FreeLookThr.tip = Mouse movement threshold before enabling free-look mode\n(while holding the right mouse button).
+ #LOC_CameraTools_CameraInertia = Camera Inertia:
+ #LOC_CameraTools_CameraInertia.tip = How loose the camera feels in inertial chase mode.
+ #LOC_CameraTools_InertialChaseMode = Inertial Chase Mode
+ #LOC_CameraTools_InertialChaseMode.tip = A looser following mode, as if the camera had inertia.
+ #LOC_CameraTools_DogfightOffsetMode = Offset Mode
+ #LOC_CameraTools_DogfightOffsetMode.tip = Which reference frame to use for the offset.
+ #LOC_CameraTools_ChasePlaneMode = Chase Plane Mode
+ #LOC_CameraTools_ChasePlaneMode.tip = A mode where the camera points at the\ntarget vessel from a "chase plane".
+ #LOC_CameraTools_ChasePlaneTarget = Target:
+ #LOC_CameraTools_ChasePlaneTarget.tip = Click to target a specific part.\nClick elsewhere to reset to CoM.
+
+ // Pathing camera
+ #LOC_CameraTools_Path = Path:
+ #LOC_CameraTools_NoPath = Path: None
+ #LOC_CameraTools_OpenPath = Open Path
+ #LOC_CameraTools_NewPath = New Path
+ #LOC_CameraTools_DeletePath = Delete Path
+ #LOC_CameraTools_SecondarySmoothing = Secondary Smoothing:
+ #LOC_CameraTools_SecondarySmoothing.tip = The amount of additional smoothing (lerp/slerp) when playing back a path.
+ #LOC_CameraTools_PathTimescale = Path Timescale:
+ #LOC_CameraTools_RealTime = Real-Time
+ #LOC_CameraTools_RealTime.tip = Use unscaled real-time to update the pathing camera.
+ #LOC_CameraTools_InGameTime = In-Game Time
+ #LOC_CameraTools_InGameTime.tip = Use scaled in-game time to update the pathing camera.
+ #LOC_CameraTools_NewKey = New Key
+ #LOC_CameraTools_NewKey.tip = Add a new keyframe.
+ #LOC_CameraTools_ToStationaryCamera = To Stationary Camera
+ #LOC_CameraTools_ToStationaryCamera.tip = Switch to the stationary camera while maintaining the camera view.
+ #LOC_CameraTools_KeyframeNum = Keyframe #
+ #LOC_CameraTools_RevertPos = Revert Pos
+ #LOC_CameraTools_RevertPos.tip = Revert to the position of the current keyframe.
+ #LOC_CameraTools_Time = Time:
+ #LOC_CameraTools_MaintainSpeed = Maintain Speed
+ #LOC_CameraTools_MaintainSpeed.tip = Adjust the timing of current frame to maintain\n(roughly) the same speed as the last frame.
+ #LOC_CameraTools_PositionInterpolation = Pos:
+ #LOC_CameraTools_RotationInterpolation = Rot:
+ #LOC_CameraTools_StandardPath = Standard Path
+ #LOC_CameraTools_StandardPath.tip = Positions and Rotations are relative to the active vessel.
+ #LOC_CameraTools_GeoSpatialPath = Geospatial Path
+ #LOC_CameraTools_GeoSpatialPath.tip = Positions are GPS coordinates and rotations are global.
+ }
+}
\ No newline at end of file
diff --git a/CameraTools/Distribution/GameData/CameraTools/Localization/zh-cn.cfg b/CameraTools/Distribution/GameData/CameraTools/Localization/zh-cn.cfg
new file mode 100644
index 00000000..532babf5
--- /dev/null
+++ b/CameraTools/Distribution/GameData/CameraTools/Localization/zh-cn.cfg
@@ -0,0 +1,161 @@
+
+Localization
+{
+ // Notes:
+ // - Strings ending in ":" get a space appended to them in StringUtils.Localize so that Localize(field, value) gives "field: value".
+ // - Localization strings ending in ".tip" contain the tooltip for the localization.
+ // - Tooltips can be split over multiple lines using "\n" characters.
+ zh-cn
+ {
+ #LOC_CameraTools_Title = 相机工具
+ #LOC_CameraTools_Version = 版本:
+ #LOC_CameraTools_Tool = 工具:
+ #LOC_CameraTools_NextMode = >
+ #LOC_CameraTools_NextMode.tip = 下一个相机模式。
+ #LOC_CameraTools_PrevMode = <
+ #LOC_CameraTools_PrevMode.tip = 上一个相机模式。
+ #LOC_CameraTools_ShowTooltips = ?
+ #LOC_CameraTools_ShowTooltips.tip = 切换工具提示。
+ #LOC_CameraTools_ToggleTextFields = #
+ #LOC_CameraTools_ToggleTextFields.tip = 切换文本字段。
+
+ //通用选项/通用
+ #LOC_CameraTools_AudioEffects = 启用音频效果
+ #LOC_CameraTools_AudioEffects.tip = 启用大气音频效果,如风声和音障。
+ #LOC_CameraTools_VisualEffects = 启用视觉效果
+ #LOC_CameraTools_VisualEffects.tip = 启用股票大气/再入视觉效果。
+ #LOC_CameraTools_AutoEnableForBDA = 自动启用BDArmory
+ #LOC_CameraTools_AutoEnableForBDA.tip = 在BDA开始比赛时自动启用CameraTools。
+ #LOC_CameraTools_Autozoom = 自动缩放
+ #LOC_CameraTools_Autozoom.tip = 自动调整缩放。
+ #LOC_CameraTools_AutozoomMargin = 自动缩放边距:
+ #LOC_CameraTools_AutozoomMargin.tip = 自动调整缩放的程度。
+ #LOC_CameraTools_CameraShake = 相机抖动:
+ #LOC_CameraTools_CameraShake.tip = 由附近飞船引起的相机抖动程度。
+ #LOC_CameraTools_RandomMode = 随机模式
+ #LOC_CameraTools_RandomMode.tip = 切换飞船时随机选择新模式。
+ #LOC_CameraTools_Save = 保存
+ #LOC_CameraTools_Save.tip = 将设置保存到GameData/CameraTools/PluginData/settings.cfg。
+ #LOC_CameraTools_Saving = 正在保存...
+ #LOC_CameraTools_Saved = 已保存。
+ #LOC_CameraTools_Reload = 重新加载
+ #LOC_CameraTools_Reload.tip = 从GameData/CameraTools/PluginData/settings.cfg重新加载设置。
+ #LOC_CameraTools_None = 无
+ #LOC_CameraTools_Clear = 清除
+ #LOC_CameraTools_Delete = 删除
+ #LOC_CameraTools_Apply = 应用
+ #LOC_CameraTools_Cancel = 取消
+
+ // 摄像机控制 / 按键绑定
+ #LOC_CameraTools_KeypadControl = 数字键盘控制
+ #LOC_CameraTools_KeypadControl.tip = 通过按键绑定切换摄像机控制(默认在数字键盘上)。
+ #LOC_CameraTools_MoveSpeed = 移动速度:
+ #LOC_CameraTools_ZoomSpeed = 缩放速度:
+ #LOC_CameraTools_ControlMode = 模式:
+ #LOC_CameraTools_ControlMode.tip = 在基于位置的移动和基于速度的移动之间切换。
+ #LOC_CameraTools_EditKeybindings = 编辑按键绑定
+ #LOC_CameraTools_Activate = 激活
+ #LOC_CameraTools_Revert = 恢复
+ #LOC_CameraTools_Menu = 菜单
+ #LOC_CameraTools_Up = 上
+ #LOC_CameraTools_Down = 下
+ #LOC_CameraTools_Forward = 前进
+ #LOC_CameraTools_Back = 后退
+ #LOC_CameraTools_Left = 左
+ #LOC_CameraTools_Right = 右
+ #LOC_CameraTools_ZoomIn = 放大
+ #LOC_CameraTools_ZoomOut = 缩小
+ #LOC_CameraTools_Modifier = 修饰键
+ #LOC_CameraTools_FreeMoveMode = 自由移动模式
+ #LOC_CameraTools_ResetRoll = 重置旋转
+ #LOC_CameraTools_ResetRoll.tip = 将相机的旋转重置为本地上方向。
+ #LOC_CameraTools_BindKey = 绑定按键
+ #LOC_CameraTools_PressAKey = 按下一个按键
+
+ // 静态相机
+ #LOC_CameraTools_MaxRelVel = 最大相对速度:
+ #LOC_CameraTools_MaxRelVel.tip = 相机相对于活动船只的最大允许速度。
+ #LOC_CameraTools_MaintainVel = 保持速度
+ #LOC_CameraTools_MaintainVel.tip = 保持相机起始速度。
+ #LOC_CameraTools_UseOrbital = 使用轨道速度
+ #LOC_CameraTools_UseOrbital.tip = 在保持速度时使用轨道速度而不是世界空间速度。
+ #LOC_CameraTools_CameraPosition = 相机位置:
+ #LOC_CameraTools_SetPositionClick = 单击设置位置
+ #LOC_CameraTools_ClearPosition = 清除位置
+ #LOC_CameraTools_Waiting = 等待中...
+ #LOC_CameraTools_AutoFlybyPos = 自动飞越位置
+ #LOC_CameraTools_AutoFlybyPos.tip = 自动选择飞越镜头的位置。
+ #LOC_CameraTools_AutoLandingPos = 自动着陆位置
+ #LOC_CameraTools_AutoLandingPos.tip = 自动选择一个适合拍摄美丽着陆的位置。
+ #LOC_CameraTools_ManualFlybyPos = 手动飞越位置
+ #LOC_CameraTools_ManualFlybyPos.tip = 使用手动指定的偏移量进行飞越拍摄。
+ #LOC_CameraTools_FwdPos = 前进:
+ #LOC_CameraTools_RightPos = 右:
+ #LOC_CameraTools_UpPos = 上:
+ #LOC_CameraTools_CameraTarget = 相机目标:
+ #LOC_CameraTools_SetTargetClick = 单击设置目标
+ #LOC_CameraTools_TargetSelf = 目标自身
+ #LOC_CameraTools_ClearTarget = 清除目标
+ #LOC_CameraTools_VesselCoM = 目标船只质心
+ #LOC_CameraTools_SaveRotation = 保存旋转
+ #LOC_CameraTools_SaveRotation.tip = 在还原时保存相机的旋转,以便下次激活静态相机时重用。
+
+ // 空战相机
+ #LOC_CameraTools_SecondaryTarget = 次要目标:
+ #LOC_CameraTools_Centroid = 质心
+ #LOC_CameraTools_BDAAutoTarget = BDA AI 自动目标
+ #LOC_CameraTools_BDAAutoTarget.tip = 让 BDArmory 决定次要目标应该是什么。
+ #LOC_CameraTools_MinimumInterval = 最小间隔
+ #LOC_CameraTools_MinimumInterval.tip = 在 BDArmory 选择新的次要目标之前的最小间隔(除非目标死亡)。
+ #LOC_CameraTools_SecondaryTargetDeathSwitchDelay = 目标死亡延迟
+ #LOC_CameraTools_SecondaryTargetDeathSwitchDelay.tip = 非导弹次要目标死亡后切换次要目标之前的延迟。
+ #LOC_CameraTools_TargetIncomingMissiles = 目标来袭导弹
+ #LOC_CameraTools_TargetIncomingMissiles.tip = 将来袭导弹作为次要目标优先考虑。
+ #LOC_CameraTools_MinimumIntervalMissiles = 最小导弹间隔
+ #LOC_CameraTools_MinimumIntervalMissiles.tip = 导弹次要目标切换的最小间隔(除非目标死亡)。
+ #LOC_CameraTools_TargetDogfightCentroid = 目标空战质心
+ #LOC_CameraTools_TargetDogfightCentroid.tip = 使用空战质心作为次要目标,而不是单个飞船。
+ #LOC_CameraTools_Distance = 距离:
+ #LOC_CameraTools_Offset = 偏移:
+ #LOC_CameraTools_X = X:
+ #LOC_CameraTools_Y = Y:
+ #LOC_CameraTools_Lerp = 插值:
+ #LOC_CameraTools_Lerp.tip = 相机跟随目标飞船的速度。
+ #LOC_CameraTools_Roll = 翻滚:
+ #LOC_CameraTools_Roll.tip = 根据目标飞船的翻滚程度倾斜相机。
+ #LOC_CameraTools_FreeLookThr = 自由视角推力:
+ #LOC_CameraTools_FreeLookThr.tip = 鼠标移动阈值,在启用自由视角模式之前\n(按住鼠标右键)。
+ #LOC_CameraTools_CameraInertia = 相机惯性:
+ #LOC_CameraTools_CameraInertia.tip = 惯性追踪模式下相机的灵活度。
+ #LOC_CameraTools_InertialChaseMode = 惯性追踪模式
+ #LOC_CameraTools_InertialChaseMode.tip = 一种更宽松的跟随模式,就像相机具有惯性一样。
+
+ // 路径相机
+ #LOC_CameraTools_Path = 路径:
+ #LOC_CameraTools_NoPath = 无路径
+ #LOC_CameraTools_OpenPath = 打开路径
+ #LOC_CameraTools_NewPath = 新路径
+ #LOC_CameraTools_DeletePath = 删除路径
+ #LOC_CameraTools_SecondarySmoothing = 二次平滑:
+ #LOC_CameraTools_SecondarySmoothing.tip = 播放路径时的额外平滑量(lerp/slerp)。
+ #LOC_CameraTools_PathTimescale = 路径时间尺度:
+ #LOC_CameraTools_RealTime = 实时
+ #LOC_CameraTools_RealTime.tip = 使用未缩放的实时时间来更新路径相机。
+ #LOC_CameraTools_InGameTime = 游戏内时间
+ #LOC_CameraTools_InGameTime.tip = 使用缩放的游戏内时间来更新路径相机。
+ #LOC_CameraTools_NewKey = 新关键帧
+ #LOC_CameraTools_NewKey.tip = 添加一个新的关键帧。
+ #LOC_CameraTools_ToStationaryCamera = 切换到静止相机
+ #LOC_CameraTools_ToStationaryCamera.tip = 在保持相机视图的同时切换到静止相机。
+ #LOC_CameraTools_KeyframeNum = 关键帧 #
+ #LOC_CameraTools_RevertPos = 恢复位置
+ #LOC_CameraTools_RevertPos.tip = 恢复到当前关键帧的位置。
+ #LOC_CameraTools_Time = 时间:
+ #LOC_CameraTools_MaintainSpeed = 保持速度
+ #LOC_CameraTools_MaintainSpeed.tip = 调整当前帧的时间,以保持与上一帧大致相同的速度。
+ #LOC_CameraTools_PositionInterpolation = 位置:
+ #LOC_CameraTools_RotationInterpolation = 旋转:
+ }
+}
+
+
diff --git a/CameraTools/Distribution/GameData/CameraTools/Plugins/CameraTools.dll b/CameraTools/Distribution/GameData/CameraTools/Plugins/CameraTools.dll
deleted file mode 100644
index 33314b9c..00000000
Binary files a/CameraTools/Distribution/GameData/CameraTools/Plugins/CameraTools.dll and /dev/null differ
diff --git a/CameraTools/Distribution/GameData/CameraTools/paths.cfg b/CameraTools/Distribution/GameData/CameraTools/paths.cfg
deleted file mode 100644
index 2da7f5cb..00000000
--- a/CameraTools/Distribution/GameData/CameraTools/paths.cfg
+++ /dev/null
@@ -1,13 +0,0 @@
-CAMERAPATHS
-{
- CAMERAPATH
- {
- pathName = New Path
- points = 13.40305,-16.60615,-4.274539;14.48815,-13.88801,-4.26651;14.48839,-13.88819,-4.267331;15.52922,-14.25925,-4.280066;
- rotations = 0.5759971,0.2491289,-0.2965982,-0.7198553;-0.6991884,0.09197949,-0.08556388,0.7038141;-0.6991884,0.09197949,-0.08556388,0.7038141;-0.6506922,0.2786613,-0.271617,0.6520521;
- times = 0;1;2;6;
- zooms = 1;2.035503;3.402367;3.402367;
- lerpRate = 3.1
- timeScale = 0.29
- }
-}
diff --git a/CameraTools/Distribution/GameData/CameraTools/settings.cfg b/CameraTools/Distribution/GameData/CameraTools/settings.cfg
deleted file mode 100644
index 1f20db77..00000000
--- a/CameraTools/Distribution/GameData/CameraTools/settings.cfg
+++ /dev/null
@@ -1,4 +0,0 @@
-CToolsSettings
-{
-
-}
diff --git a/CameraTools/Extensions/PartExtensions.cs b/CameraTools/Extensions/PartExtensions.cs
new file mode 100644
index 00000000..86a2fbe5
--- /dev/null
+++ b/CameraTools/Extensions/PartExtensions.cs
@@ -0,0 +1,33 @@
+namespace CameraTools
+{
+ public static class PartExtensions
+ {
+ ///
+ /// KSP version dependent query of whether the part is a kerbal on EVA.
+ ///
+ /// Part to check.
+ /// true if the part is a kerbal on EVA.
+ public static bool IsKerbalEVA(this Part part)
+ {
+ if (part == null) return false;
+ if ((Versioning.version_major == 1 && Versioning.version_minor > 10) || Versioning.version_major > 1) // Introduced in 1.11
+ {
+ return part.IsKerbalEVA_1_11();
+ }
+ else
+ {
+ return part.IsKerbalEVA_pre_1_11();
+ }
+ }
+
+ private static bool IsKerbalEVA_1_11(this Part part) // KSP has issues on older versions if this call is in the parent function.
+ {
+ return part.isKerbalEVA();
+ }
+
+ private static bool IsKerbalEVA_pre_1_11(this Part part)
+ {
+ return part.FindModuleImplementing() != null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CameraTools/Extensions/VesselExtensions.cs b/CameraTools/Extensions/VesselExtensions.cs
new file mode 100644
index 00000000..96e78994
--- /dev/null
+++ b/CameraTools/Extensions/VesselExtensions.cs
@@ -0,0 +1,40 @@
+using System.Collections.Generic;
+
+namespace CameraTools
+{
+ public static class VesselExtensions
+ {
+ public static HashSet InOrbitSituations = new HashSet { Vessel.Situations.ORBITING, Vessel.Situations.SUB_ORBITAL, Vessel.Situations.ESCAPING };
+ public static bool InOrbit(this Vessel v)
+ {
+ if (v == null) return false;
+ return InOrbitSituations.Contains(v.situation);
+ }
+
+ public static Vector3d Velocity(this Vessel v)
+ {
+ if (v == null) return Vector3d.zero;
+ if (!v.InOrbit())
+ {
+ return v.srf_velocity;
+ }
+ else
+ {
+ return v.obt_velocity;
+ }
+ }
+
+ public static double Speed(this Vessel v)
+ {
+ if (v == null) return 0;
+ if (!v.InOrbit())
+ {
+ return v.srfSpeed;
+ }
+ else
+ {
+ return v.obt_speed;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CameraTools/InputField.cs b/CameraTools/InputField.cs
new file mode 100644
index 00000000..7602b8b3
--- /dev/null
+++ b/CameraTools/InputField.cs
@@ -0,0 +1,81 @@
+using UnityEngine;
+using System;
+using System.Collections;
+
+namespace CameraTools
+{
+ // C# generics doesn't have a generic numeric type, so we can't do this generically.
+ public class FloatInputField : MonoBehaviour
+ {
+ public FloatInputField Initialise(double lastUpdated, float currentValue, float minValue = float.MinValue, float maxValue = float.MaxValue, int precision = 6)
+ {
+ this.lastUpdated = lastUpdated;
+ this.currentValue = currentValue;
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ this.precision = precision;
+ return this;
+ }
+ public double lastUpdated;
+ public string possibleValue = string.Empty;
+ private float _value;
+ public float currentValue { get { return _value; } set { _value = value; possibleValue = _value.ToString($"G{precision}"); } }
+ private float minValue;
+ private float maxValue;
+ private int precision;
+ private bool coroutineRunning = false;
+ private Coroutine coroutine;
+
+ public void tryParseValue(string v)
+ {
+ if (v != possibleValue)
+ {
+ lastUpdated = !string.IsNullOrEmpty(v) ? Time.time : Time.time + 0.5; // Give the empty string an extra 0.5s.
+ possibleValue = v;
+ if (!coroutineRunning)
+ {
+ coroutine = StartCoroutine(UpdateValueCoroutine());
+ }
+ }
+ }
+
+ IEnumerator UpdateValueCoroutine()
+ {
+ coroutineRunning = true;
+ while (Time.time - lastUpdated < 0.5)
+ yield return new WaitForFixedUpdate();
+ tryParseCurrentValue();
+ coroutineRunning = false;
+ yield return new WaitForFixedUpdate();
+ }
+
+ void tryParseCurrentValue()
+ {
+ float newValue;
+ if (float.TryParse(possibleValue, out newValue))
+ {
+ currentValue = Math.Min(Math.Max(newValue, minValue), maxValue);
+ lastUpdated = Time.time;
+ }
+ possibleValue = currentValue.ToString($"G{precision}");
+ }
+
+ // Parse the current possible value immediately.
+ public void tryParseValueNow()
+ {
+ tryParseCurrentValue();
+ if (coroutineRunning)
+ {
+ StopCoroutine(coroutine);
+ coroutineRunning = false;
+ }
+ }
+
+ // Update the min aand max values. Note: if min > max, then the max value will always be set.
+ public void UpdateLimits(float minValue, float maxValue)
+ {
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CameraTools/ModIntegration/BDArmory.cs b/CameraTools/ModIntegration/BDArmory.cs
new file mode 100644
index 00000000..6a77ef5c
--- /dev/null
+++ b/CameraTools/ModIntegration/BDArmory.cs
@@ -0,0 +1,802 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System;
+using UnityEngine;
+
+using CameraTools.Utils;
+
+namespace CameraTools.ModIntegration
+{
+ [KSPAddon(KSPAddon.Startup.Flight, false)]
+ public class BDArmory : MonoBehaviour
+ {
+ #region Public fields
+ public static BDArmory instance;
+ public static bool hasBDA = false;
+
+ [CTPersistantField] public bool autoEnableForBDA = true;
+ [CTPersistantField] public bool useBDAutoTarget = true;
+ [CTPersistantField] public bool autoTargetIncomingMissiles = false;
+ [CTPersistantField] public bool useCentroid = false;
+ public bool autoEnableOverride = false;
+ public bool hasBDAI = false;
+ public bool hasPilotAI = false;
+ public AIType aiType = AIType.Unknown;
+ public enum AIType { Pilot, Surface, VTOL, Orbital, Unknown };
+ public bool isBDMissile = false;
+ public List bdWMVessels
+ {
+ get
+ {
+ if (hasBDA && (_bdWMVessels is null || Time.time - _bdWMVesselsLastUpdate > 1f)) GetBDVessels(); // Update once per second.
+ return _bdWMVessels;
+ }
+ }
+ public float restoreDistanceLimit = float.MaxValue; // Limit the distance to restore the camera to (for auto-enabling).
+ public bool isRunningWaypoints = false;
+ #endregion
+
+ #region Private fields
+ CamTools camTools => CamTools.fetch;
+ Vessel vessel => CamTools.fetch.vessel;
+ public Dictionary inputFields;
+ object bdCompetitionInstance = null;
+ Func