From 267070ca6f78d787e24616f485ab3d46d585069e Mon Sep 17 00:00:00 2001 From: Scott Manley Date: Wed, 20 May 2020 18:18:37 -0700 Subject: [PATCH 001/263] random camera angle mode --- CameraTools/CamTools.cs | 215 ++++++++++++++++++++++++++++++---------- 1 file changed, 161 insertions(+), 54 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index b0e95d42..38aca762 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -53,6 +53,12 @@ public class CamTools : MonoBehaviour [CTPersistantField] public ToolModes toolMode = ToolModes.StationaryCamera; + [CTPersistantField] + public bool randomMode = false; + + bool temporaryRevert = false; + + Rect windowRect = new Rect(0,0,0,0); bool gameUIToggle = true; float incrButtonWidth = 26; @@ -183,6 +189,7 @@ public class CamTools : MonoBehaviour object aiComponent = null; FieldInfo bdAiTargetField; + double targetUpdateTime = 0; //pathing int selectedPathIndex = -1; @@ -216,6 +223,8 @@ float pathTime } Vector2 keysScrollPos; + private System.Random rng; + void Awake() { if(fetch) @@ -232,6 +241,7 @@ void Awake() guiOffsetUp = manualOffsetUp.ToString(); guiKeyZoomSpeed = keyZoomSpeed.ToString(); guiFreeMoveSpeed = freeMoveSpeed.ToString(); + rng = new System.Random(); } void Start() @@ -281,47 +291,14 @@ void Update() if(Input.GetKeyDown(revertKey)) { + temporaryRevert = false; RevertCamera(); } else if(Input.GetKeyDown(cameraKey)) { - if(toolMode == ToolModes.StationaryCamera) - { - if(!cameraToolActive) - { - SaveOriginalCamera(); - StartStationaryCamera(); - } - else - { - //RevertCamera(); - StartStationaryCamera(); - } - } - else if(toolMode == ToolModes.DogfightCamera) - { - if(!cameraToolActive) - { - SaveOriginalCamera(); - StartDogfightCamera(); - } - else - { - StartDogfightCamera(); - } - } - else if(toolMode == ToolModes.Pathing) - { - if(!cameraToolActive) - { - SaveOriginalCamera(); - } - StartPathingCam(); - PlayPathingCam(); - } + temporaryRevert = true; + cameraActivate(); } - - } if(Input.GetMouseButtonUp(0)) @@ -365,17 +342,56 @@ void Update() waitingForPosition = false; } - - - + + + + } + + + private void cameraActivate() + { + if (toolMode == ToolModes.StationaryCamera) + { + if (!cameraToolActive) + { + SaveOriginalCamera(); + StartStationaryCamera(); + } + else + { + //RevertCamera(); + StartStationaryCamera(); + } + } + else if (toolMode == ToolModes.DogfightCamera) + { + if (!cameraToolActive) + { + SaveOriginalCamera(); + StartDogfightCamera(); + } + else + { + StartDogfightCamera(); + } + } + else if (toolMode == ToolModes.Pathing) + { + if (!cameraToolActive) + { + SaveOriginalCamera(); + } + StartPathingCam(); + PlayPathingCam(); + } + } public void ShakeCamera(float magnitude) { shakeMagnitude = Mathf.Max(shakeMagnitude, magnitude); } - - + int posCounter = 0;//debug void FixedUpdate() { @@ -426,13 +442,14 @@ void FixedUpdate() dogfightTarget = null; if(cameraToolActive) { + Debug.Log("[CameraTools] Reverting Because dogfightTarget is null"); RevertCamera(); } } } - if(hasDied && Time.time-diedTime > 2) + if(hasDied && Time.time-diedTime > 3) { RevertCamera(); } @@ -450,7 +467,13 @@ void StartDogfightCamera() if(!dogfightTarget) { - dogfightVelocityChase = true; + if (rng.Next(3) == 0) + { + dogfightVelocityChase = false; // sometimes throw in a non chase angle + } + else { + dogfightVelocityChase = true; + } } else { @@ -484,6 +507,7 @@ void UpdateDogfightCamera() { if(!vessel || (!dogfightTarget && !dogfightLastTarget && !dogfightVelocityChase)) { + Debug.Log("[CameraTools] Reverting during UpdateDogfightCamera()"); RevertCamera(); return; } @@ -633,10 +657,15 @@ void UpdateDogfightCamera() if(hasBDAI && useBDAutoTarget) { - Vessel newAITarget = GetAITargetedVessel(); - if(newAITarget) + // don't update targets too quickly + if (Planetarium.GetUniversalTime() - targetUpdateTime > 5) { - dogfightTarget = newAITarget; + Vessel newAITarget = GetAITargetedVessel(); + if (newAITarget) + { + dogfightTarget = newAITarget; + } + targetUpdateTime = Planetarium.GetUniversalTime(); } } @@ -1015,8 +1044,11 @@ void StartStationaryCamera() flightCamera.DeactivateUpdate(); cameraParent.transform.position = vessel.transform.position+vessel.rb_velocity*Time.fixedDeltaTime; manualPosition = Vector3.zero; - - + if(randomMode) + { + camTarget = FlightGlobals.ActiveVessel.GetReferenceTransformPart(); + hasTarget = true; + } hasTarget = (camTarget != null) ? true : false; @@ -1024,7 +1056,7 @@ void StartStationaryCamera() //Vector3 upAxis = flightCamera.transform.up; - if(autoFlybyPosition) + if(autoFlybyPosition || randomMode) { setPresetOffset = false; Vector3 velocity = vessel.srf_velocity; @@ -1038,6 +1070,7 @@ void StartStationaryCamera() 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); @@ -1109,6 +1142,7 @@ void StartStationaryCamera() SetDoppler(true); AddAtmoAudioControllers(true); + } else { @@ -1151,13 +1185,22 @@ void RevertCamera() cameraToolActive = false; + + StopPlayingPath(); + ResetDoppler(); - if(OnResetCTools != null) + + try { - OnResetCTools(); + if (OnResetCTools != null) + { + OnResetCTools(); + } + } catch (Exception e) + { + Debug.Log("[CamTools] Caught exception resetting CTools " + e.ToString()); } - StopPlayingPath(); } void SaveOriginalCamera() @@ -1586,7 +1629,9 @@ void GuiWindow(int windowID) } line += 1.25f; + randomMode = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), randomMode, "Random Mode"); + line += 1.25f; enableKeypad = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), enableKeypad, "Keypad Control"); if(enableKeypad) { @@ -2006,20 +2051,82 @@ void SwitchToVessel(Vessel v) vessel = v; CheckForBDAI(v); - + // reactivate camera if it was reverted + if(temporaryRevert && randomMode) + { + cameraToolActive = true; + toolMode = ToolModes.Pathing; + } if(cameraToolActive) { - if(toolMode == ToolModes.DogfightCamera) + targetUpdateTime = Planetarium.GetUniversalTime(); + + if (hasBDAI && useBDAutoTarget) + { + Vessel newAITarget = GetAITargetedVessel(); + if (newAITarget) + { + dogfightTarget = newAITarget; + } + } + if (randomMode) + { + var lowAlt = 100.0; + if(vessel.verticalSpeed < -20) + { + lowAlt = vessel.verticalSpeed * -5; + } + if (vessel != null && vessel.terrainAltitude < lowAlt) + { + RevertCamera(); + } + else + { + var oldToolMode = toolMode; + var rand = rng.Next(5); + if (rand == 0) + { + toolMode = ToolModes.StationaryCamera; + StartStationaryCamera(); + } + else if (rand == 1) + { + RevertCamera(); // but temporaryRevert will remain true + } + else + { + toolMode = ToolModes.DogfightCamera; + } + if (cameraToolActive && toolMode != oldToolMode) + { + // recover and change to new mode + RevertCamera(); + cameraActivate(); + } + } + } + + if (toolMode == ToolModes.DogfightCamera) { StartCoroutine(ResetDogfightCamRoutine()); } + } } IEnumerator ResetDogfightCamRoutine() { yield return new WaitForEndOfFrame(); + RevertCamera(); + if (hasBDAI && useBDAutoTarget) + { + Vessel newAITarget = GetAITargetedVessel(); + if (newAITarget) + { + dogfightTarget = newAITarget; + } + } StartDogfightCamera(); } From 3694cddd4ecfc76ba9ec59fab6d41e2ac39b9a3f Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 5 Jul 2020 16:39:51 +0200 Subject: [PATCH 002/263] Fix null reference exceptions in OnResetCTools. Disable non-chase viewpoints when randomMode is disabled. --- CameraTools/CTPartAudioController.cs | 9 ++++++--- CameraTools/CamTools.cs | 2 +- CameraTools/bin/Release/CameraTools.dll | Bin 54784 -> 56320 bytes 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CameraTools/CTPartAudioController.cs b/CameraTools/CTPartAudioController.cs index 17348766..93fe5175 100644 --- a/CameraTools/CTPartAudioController.cs +++ b/CameraTools/CTPartAudioController.cs @@ -89,9 +89,12 @@ void OnDestroy() void OnResetCTools() { - audioSource.minDistance = origMinDist; - audioSource.maxDistance = origMaxDist; - audioSource.rolloffMode = origRolloffMode; + if (audioSource != null) + { + audioSource.minDistance = origMinDist; + audioSource.maxDistance = origMaxDist; + audioSource.rolloffMode = origRolloffMode; + } Destroy(this); } diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 38aca762..0679a74b 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -467,7 +467,7 @@ void StartDogfightCamera() if(!dogfightTarget) { - if (rng.Next(3) == 0) + if (randomMode && rng.Next(3) == 0) { dogfightVelocityChase = false; // sometimes throw in a non chase angle } diff --git a/CameraTools/bin/Release/CameraTools.dll b/CameraTools/bin/Release/CameraTools.dll index 43b1d4273343907260968c81647dc948acc414d4..aa88c6be9ccaa2b33e00313204ffccb4a00422e1 100644 GIT binary patch literal 56320 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 Nv(r!e`hVfTzX3MMHhBO5 literal 54784 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 Gfj Date: Thu, 30 Jul 2020 22:34:36 +0200 Subject: [PATCH 003/263] .csproj build file --- CameraTools/CameraTools.csproj | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/CameraTools/CameraTools.csproj b/CameraTools/CameraTools.csproj index 49969b6e..41ee0e84 100644 --- a/CameraTools/CameraTools.csproj +++ b/CameraTools/CameraTools.csproj @@ -2,7 +2,7 @@ Debug - x86 + AnyCPU 9.0.21022 2.0 {446E2470-00DC-4835-B62D-9DB8A7D41F4A} @@ -47,14 +47,14 @@ full AnyCPU prompt - MinimumRecommendedRules.ruleset + false bin\Release\ AnyCPU prompt - MinimumRecommendedRules.ruleset + true false portable @@ -152,11 +152,6 @@ - - - - - @@ -165,7 +160,7 @@ - + \ No newline at end of file From 775abc6a094ff5e28b6e6bdd1cdd6bf04f1b8539 Mon Sep 17 00:00:00 2001 From: josuenos Date: Thu, 30 Jul 2020 19:54:58 -0400 Subject: [PATCH 004/263] Updated CamTools.cs to target threat vessels and incoming missiles with the camera. Priority is: 1. Incoming Missiles 2. If under fire, threat vessel 3. Otherwise target vessel --- CameraTools/CamTools.cs | 143 ++++++++++++++++++++++- CameraTools/CameraTools.csproj | 201 +++++++++++++++++++++++++++++---- 2 files changed, 322 insertions(+), 22 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 0679a74b..febaef8f 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -184,10 +184,16 @@ public class CamTools : MonoBehaviour bool dogfightVelocityChase = false; //bdarmory bool hasBDAI = false; + bool hasBDWM = false; [CTPersistantField] public bool useBDAutoTarget = false; object aiComponent = null; + object wmComponent = null; FieldInfo bdAiTargetField; + FieldInfo bdWmThreatField; + FieldInfo bdWmMissileField; + FieldInfo bdWmUnderFireField; + FieldInfo bdWmUnderAttackField; double targetUpdateTime = 0; @@ -270,8 +276,13 @@ void Start() vessel = FlightGlobals.ActiveVessel; CheckForBDAI(FlightGlobals.ActiveVessel); + CheckForBDWM(FlightGlobals.ActiveVessel); } bdAiTargetField = GetAITargetField(); + bdWmThreatField = GetThreatField(); + bdWmMissileField = GetMissileField(); + bdWmUnderFireField = GetUnderFireField(); + bdWmUnderAttackField = GetUnderAttackField(); GameEvents.onVesselChange.Add(SwitchToVessel); } @@ -1995,6 +2006,24 @@ private void CheckForBDAI(Vessel v) } } + private void CheckForBDWM(Vessel v) + { + hasBDWM = false; + wmComponent = null; + if (v) + { + foreach (Part p in v.parts) + { + if (p.GetComponent("MissileFire")) + { + hasBDWM = true; + wmComponent = (object)p.GetComponent("MissileFire"); + return; + } + } + } + } + private Vessel GetAITargetedVessel() { if(!hasBDAI || aiComponent==null || bdAiTargetField==null) @@ -2002,7 +2031,20 @@ private Vessel GetAITargetedVessel() return null; } - return (Vessel) bdAiTargetField.GetValue(aiComponent); + if (hasBDWM && wmComponent != null && bdWmThreatField != null) + { + bool underFire = (bool)bdWmUnderFireField.GetValue(wmComponent); + bool underAttack = (bool)bdWmUnderAttackField.GetValue(wmComponent); + + if (bdWmMissileField != null) + return (Vessel)bdWmMissileField.GetValue(wmComponent); + else if (underFire || underAttack) + return (Vessel)bdWmThreatField.GetValue(wmComponent); + else + return (Vessel)bdAiTargetField.GetValue(aiComponent); + } + + return (Vessel)bdAiTargetField.GetValue(aiComponent); } private Type AIModuleType() @@ -2026,6 +2068,104 @@ private Type AIModuleType() return null; } + + private Type WeaponManagerType() + { + Debug.Log("loaded assy's: "); + foreach (var assy in AssemblyLoader.loadedAssemblies) + { + Debug.Log("- "+assy.assembly.FullName); + if (assy.assembly.FullName.Contains("BDArmory")) + { + foreach (var t in assy.assembly.GetTypes()) + { + if (t.Name == "MissileFire") + { + return t; + } + } + } + } + + return null; + } + + private FieldInfo GetThreatField() + { + Type wmModType = WeaponManagerType(); + if (wmModType == null) return null; + + FieldInfo[] fields = wmModType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); + //Debug.Log("bdai fields: "); + foreach (var f in fields) + { + // Debug.Log("- " + f.Name); + if (f.Name == "incomingThreatVessel") + { + return f; + } + } + + return null; + } + + private FieldInfo GetMissileField() + { + Type wmModType = WeaponManagerType(); + if (wmModType == null) return null; + + FieldInfo[] fields = wmModType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); + //Debug.Log("bdai fields: "); + foreach (var f in fields) + { + // Debug.Log("- " + f.Name); + if (f.Name == "incomingMissileVessel") + { + return f; + } + } + + return null; + } + + private FieldInfo GetUnderFireField() + { + Type wmModType = WeaponManagerType(); + if (wmModType == null) return null; + + FieldInfo[] fields = wmModType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); + //Debug.Log("bdai fields: "); + foreach (var f in fields) + { + //Debug.Log("- " + f.Name); + if (f.Name == "underFire") + { + return f; + } + } + + return null; + } + + private FieldInfo GetUnderAttackField() + { + Type wmModType = WeaponManagerType(); + if (wmModType == null) return null; + + FieldInfo[] fields = wmModType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); + //Debug.Log("bdai fields: "); + foreach (var f in fields) + { + //Debug.Log("- " + f.Name); + if (f.Name == "underAttack") + { + return f; + } + } + + return null; + } + private FieldInfo GetAITargetField() { Type aiModType = AIModuleType(); @@ -2063,6 +2203,7 @@ void SwitchToVessel(Vessel v) if (hasBDAI && useBDAutoTarget) { + CheckForBDWM(v); Vessel newAITarget = GetAITargetedVessel(); if (newAITarget) { diff --git a/CameraTools/CameraTools.csproj b/CameraTools/CameraTools.csproj index 41ee0e84..fea88e38 100644 --- a/CameraTools/CameraTools.csproj +++ b/CameraTools/CameraTools.csproj @@ -78,69 +78,228 @@ False - ..\..\_LocalDev\KSPRefs\Assembly-CSharp.dll + ..\..\..\KSP DLLs\Assembly-CSharp.dll + + + ..\..\..\KSP DLLs\Assembly-CSharp-firstpass.dll False - ..\..\_LocalDev\KSPRefs\KSPAssets.dll + ..\..\..\KSP DLLs\KSPAssets.dll + + + ..\..\..\KSP DLLs\KSPAssets.XmlSerializers.dll + + + ..\..\..\KSP DLLs\KSPTrackIR.dll + + ..\..\..\KSP DLLs\Unity.Analytics.DataPrivacy.dll + + + ..\..\..\KSP DLLs\Unity.Analytics.StandardEvents.dll + + + ..\..\..\KSP DLLs\Unity.Analytics.Tracker.dll + + + ..\..\..\KSP DLLs\Unity.RenderPipelines.Core.Runtime.dll + + + ..\..\..\KSP DLLs\Unity.RenderPipelines.Core.ShaderLibrary.dll + + + ..\..\..\KSP DLLs\Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary.dll + + + ..\..\..\KSP DLLs\Unity.Timeline.dll + False - ..\..\_LocalDev\KSPRefs\UnityEngine.dll + ..\..\..\KSP DLLs\UnityEngine.dll + + + ..\..\..\KSP DLLs\UnityEngine.AccessibilityModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.AIModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.AndroidJNIModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.AnimationModule.dll + ..\..\..\KSP DLLs\UnityEngine.AnimationModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.ARModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.AssetBundleModule.dll + ..\..\..\KSP DLLs\UnityEngine.AssetBundleModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.AudioModule.dll + ..\..\..\KSP DLLs\UnityEngine.AudioModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.ClothModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.ClusterInputModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.ClusterRendererModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.CoreModule.dll + ..\..\..\KSP DLLs\UnityEngine.CoreModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.CrashReportingModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.DirectorModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.DSPGraphModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.FileSystemHttpModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.GameCenterModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.GridModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.HotReloadModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.ImageConversionModule.dll + ..\..\..\KSP DLLs\UnityEngine.ImageConversionModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.IMGUIModule.dll + ..\..\..\KSP DLLs\UnityEngine.IMGUIModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.InputLegacyModule.dll + ..\..\..\KSP DLLs\UnityEngine.InputLegacyModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.InputModule.dll + ..\..\..\KSP DLLs\UnityEngine.InputModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.JSONSerializeModule.dll + ..\..\..\KSP DLLs\UnityEngine.JSONSerializeModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.LocalizationModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.ParticleSystemModule.dll + ..\..\..\KSP DLLs\UnityEngine.ParticleSystemModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.PerformanceReportingModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.Physics2DModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.PhysicsModule.dll + ..\..\..\KSP DLLs\UnityEngine.PhysicsModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.ProfilerModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.ScreenCaptureModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.SharedInternalsModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.SpriteMaskModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.SpriteShapeModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.StreamingModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.SubstanceModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.TerrainModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.TerrainPhysicsModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.TextCoreModule.dll + ..\..\..\KSP DLLs\UnityEngine.TextCoreModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.TextRenderingModule.dll + ..\..\..\KSP DLLs\UnityEngine.TextRenderingModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.TilemapModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.TLSModule.dll False - ..\..\_LocalDev\KSPRefs\UnityEngine.UI.dll + ..\..\..\KSP DLLs\UnityEngine.UI.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.UIElementsModule.dll + ..\..\..\KSP DLLs\UnityEngine.UIElementsModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.UIModule.dll + ..\..\..\KSP DLLs\UnityEngine.UIModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.UmbraModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.UNETModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.UnityAnalyticsModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.UnityConnectModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.UnityTestProtocolModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.UnityWebRequestAudioModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.UnityWebRequestModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.UnityWebRequestTextureModule.dll - ..\..\_LocalDev\KSPRefs\UnityEngine.UnityWebRequestWWWModule.dll + ..\..\..\KSP DLLs\UnityEngine.UnityWebRequestWWWModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.VehiclesModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.VFXModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.VideoModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.VRModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.WindModule.dll + + + ..\..\..\KSP DLLs\UnityEngine.XRModule.dll @@ -160,7 +319,7 @@ - + + + @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/*.*" + + export KSP_DIR="`cat $(ProjectDir)../../_LocalDev/ksp_dir.txt`" + 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" + + echo Build/deploy complete! + + \ 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/Plugins/CameraTools.dll b/CameraTools/Distribution/GameData/CameraTools/Plugins/CameraTools.dll deleted file mode 100644 index 33314b9c731bdf4503bd58625bf2cf22536be1f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54784 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 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 -{ - -} From 871faba2040bb9bf30cbd1055761b1f3dc061609 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Mon, 16 Nov 2020 00:56:04 +0100 Subject: [PATCH 018/263] Adjustable dogfight lerp rate in settings. Lerp from previous camera position/rotation instead of origin. Clean up some unnecessary computations. --- CameraTools/CamTools.cs | 53 +++++++++++++++++++++------------------ CameraTools/CameraPath.cs | 2 +- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index a7152687..19ac54d5 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -118,7 +118,7 @@ public class CamTools : MonoBehaviour [CTPersistantField] public float dogfightOffsetX = 10; [CTPersistantField] public float dogfightOffsetY = 4; float dogfightMaxOffset = 50; - float dogfightLerp = 20; + [CTPersistantField] public float dogfightLerp = 0.2f; [CTPersistantField] public float autoZoomMargin = 20; List loadedVessels; bool showingVesselList = false; @@ -227,8 +227,6 @@ void Start() GameEvents.onGameSceneLoadRequested.Add(PostDeathRevert); cameraParent = new GameObject("StationaryCameraParent"); - //cameraParent.SetActive(true); - //cameraParent = (GameObject) Instantiate(cameraParent, Vector3.zero, Quaternion.identity); deathCam = new GameObject("DeathCam"); if (FlightGlobals.ActiveVessel != null) @@ -321,10 +319,10 @@ void Update() void FixedUpdate() { + // Note: we have to perform 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) return; - // When the current target vessel dies, something external to CameraTools fights for control of the flightCamera, which is what causes the spazzing out. So, do nothing until a vessel change occurs. - if (hasDied && cameraToolActive) return; + if (hasDied && cameraToolActive) return; // Do nothing until we have an active vessel. if (FlightGlobals.ActiveVessel != null && (vessel == null || vessel != FlightGlobals.ActiveVessel)) { @@ -464,10 +462,13 @@ void StartDogfightCamera() vessel = FlightGlobals.ActiveVessel; cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.CoM).normalized; + cameraParent.transform.position = deathCam.transform.position; // First update the cameraParent to the last deathCam configuration + cameraParent.transform.rotation = deathCam.transform.rotation; flightCamera.SetTargetNone(); flightCamera.transform.parent = cameraParent.transform; flightCamera.DeactivateUpdate(); - cameraParent.transform.position = vessel.transform.position + vessel.rb_velocity * Time.fixedDeltaTime; + cameraParent.transform.position = vessel.transform.position; // Then adjust the flightCamera for the new parent. + flightCamera.transform.localRotation = Quaternion.identity; cameraToolActive = true; @@ -501,7 +502,7 @@ void UpdateDogfightCamera() dogfightLastTargetPosition += dogfightLastTargetVelocity * Time.fixedDeltaTime; } - cameraParent.transform.position = (vessel.CoM - (vessel.rb_velocity * Time.fixedDeltaTime)); + cameraParent.transform.position = vessel.CoM; if (dogfightVelocityChase) { @@ -519,13 +520,13 @@ void UpdateDogfightCamera() 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); + flightCamera.transform.localPosition = Vector3.Lerp(flightCamera.transform.localPosition, localCamPos, dogfightLerp); //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); + flightCamera.transform.rotation = Quaternion.Lerp(flightCamera.transform.rotation, camRot, dogfightLerp); //autoFov if (autoFOV) @@ -537,7 +538,7 @@ void UpdateDogfightCamera() } else { - float angle = Vector3.Angle((dogfightLastTargetPosition + (dogfightLastTargetVelocity * Time.fixedDeltaTime)) - flightCamera.transform.position, (vessel.CoM + (vessel.rb_velocity * Time.fixedDeltaTime)) - flightCamera.transform.position); + float angle = Vector3.Angle(dogfightLastTargetPosition - flightCamera.transform.position, vessel.CoM - flightCamera.transform.position); targetFoV = Mathf.Clamp(angle + autoZoomMargin, 0.1f, 60f); } manualFOV = targetFoV; @@ -671,10 +672,14 @@ void StartStationaryCamera() cameraUp = Vector3.up; } + cameraParent.transform.position = deathCam.transform.position; // First update the cameraParent to the last deathCam configuration + cameraParent.transform.rotation = deathCam.transform.rotation; flightCamera.SetTargetNone(); flightCamera.transform.parent = cameraParent.transform; flightCamera.DeactivateUpdate(); - cameraParent.transform.position = vessel.transform.position + vessel.rb_velocity * Time.fixedDeltaTime; + cameraParent.transform.position = vessel.transform.position; // Then adjust the flightCamera for the new parent. + flightCamera.transform.localRotation = Quaternion.identity; + manualPosition = Vector3.zero; if (randomMode) { @@ -781,7 +786,6 @@ void StartStationaryCamera() Debug.Log("[CameraTools]: Stationary Camera failed. Active Vessel is null."); } resetPositionFix = flightCamera.transform.position; - Debug.Log("[CameraTools]: flightCamera position post init: " + flightCamera.transform.position); } void UpdateStationaryCamera() @@ -812,12 +816,6 @@ void UpdateStationaryCamera() lookPosition = camTarget.vessel.CoM; } - lookPosition += 2 * camTarget.vessel.rb_velocity * Time.fixedDeltaTime; - if (targetCoM) - { - lookPosition += camTarget.vessel.rb_velocity * Time.fixedDeltaTime; - } - flightCamera.transform.rotation = Quaternion.LookRotation(lookPosition - flightCamera.transform.position, cameraUp); lastTargetPosition = lookPosition; } @@ -828,7 +826,7 @@ void UpdateStationaryCamera() if (vessel != null) { - cameraParent.transform.position = manualPosition + (vessel.CoM - vessel.rb_velocity * Time.fixedDeltaTime); + cameraParent.transform.position = manualPosition + vessel.CoM; if (referenceMode == ReferenceModes.Surface) { @@ -973,7 +971,7 @@ void StartPathingCam() cameraUp = Vector3.up; } - cameraParent.transform.position = vessel.transform.position + vessel.rb_velocity * Time.fixedDeltaTime; + cameraParent.transform.position = vessel.transform.position; cameraParent.transform.rotation = vessel.transform.rotation; flightCamera.SetTargetNone(); flightCamera.transform.parent = cameraParent.transform; @@ -984,15 +982,15 @@ void StartPathingCam() void UpdatePathingCam() { - cameraParent.transform.position = vessel.transform.position + vessel.rb_velocity * Time.fixedDeltaTime; + cameraParent.transform.position = vessel.transform.position; cameraParent.transform.rotation = vessel.transform.rotation; if (isPlayingPath) { 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); + flightCamera.transform.localPosition = Vector3.Lerp(flightCamera.transform.localPosition, tf.position, currentPath.lerpRate); + flightCamera.transform.localRotation = Quaternion.Slerp(flightCamera.transform.localRotation, tf.rotation, currentPath.lerpRate); + zoomExp = Mathf.Lerp(zoomExp, tf.zoom, currentPath.lerpRate); } else { @@ -1243,7 +1241,7 @@ void UpdateCameraShake() 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, 5 * Time.fixedDeltaTime); + shakeMagnitude = Mathf.Lerp(shakeMagnitude, 0, 0.1f); } public void VesselCameraShake(Vessel vessel) @@ -1840,6 +1838,11 @@ void GuiWindow(int windowID) dogfightOffsetY = (int)(GUI.HorizontalSlider(new Rect(leftIndent + 15, contentTop + (line * entryHeight) + 6, contentWidth - 45, entryHeight), dogfightOffsetY, -dogfightMaxOffset, dogfightMaxOffset) * 2) / 2f; GUI.Label(new Rect(leftIndent + contentWidth - 25, contentTop + (line * entryHeight), 25, entryHeight), dogfightOffsetY.ToString("0.0")); line += 1.5f; + + GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), 30, entryHeight), "Lerp: "); + dogfightLerp = (int)GUI.HorizontalSlider(new Rect(leftIndent + 30, contentTop + (line * entryHeight) + 6, contentWidth - 60, entryHeight), dogfightLerp * 100f, 1f, 50f) / 100f; + GUI.Label(new Rect(leftIndent + contentWidth - 25, contentTop + (line * entryHeight), 25, entryHeight), dogfightLerp.ToString("0.00")); + line += 1f; } else if (toolMode == ToolModes.Pathing) { diff --git a/CameraTools/CameraPath.cs b/CameraTools/CameraPath.cs index 8571a227..2fa01ce4 100644 --- a/CameraTools/CameraPath.cs +++ b/CameraTools/CameraPath.cs @@ -24,7 +24,7 @@ private set public List times; public List zooms; - public float lerpRate = 15; + public float lerpRate = 0.3f; public float timeScale = 1; Vector3Animation pointCurve; From 6451b967b62f558902b8a3393663ab6ce258764a Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 22 Nov 2020 16:42:28 +0100 Subject: [PATCH 019/263] Auto-enable option for BDArmory. --- .gitignore | 9 +- CameraTools/CamTools.cs | 108 +++++++++++++++++++++++- CameraTools/bin/Release/CameraTools.dll | Bin 56320 -> 0 bytes 3 files changed, 109 insertions(+), 8 deletions(-) delete mode 100644 CameraTools/bin/Release/CameraTools.dll diff --git a/.gitignore b/.gitignore index f37a8464..52352c9c 100644 --- a/.gitignore +++ b/.gitignore @@ -236,7 +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 -/CameraTools/Distribution/GameData/CameraTools/Plugins/CameraTools.dll +/.vscode +/.envrc +/CameraTools/Distribution/GameData/CameraTools/Plugins +/CameraTools/bin/Debug +/CameraTools/bin/Release diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 19ac54d5..1a0de2ae 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -24,6 +24,13 @@ public class CamTools : MonoBehaviour Vector3 cameraUp = Vector3.up; bool cameraToolActive = false; private System.Random rng; + [CTPersistantField] public bool autoEnableForBDA = false; + private bool autoEnableOverriden = false; + private bool autoEnableOverrideWhileSpawning = false; + Type bdCompetitionType = null; + object bdCompetitionInstance = null; + Type bdVesselSpawnerType = null; + object bdVesselSpawnerInstance = null; #region Input [CTPersistantField] public string cameraKey = "home"; @@ -229,6 +236,7 @@ void Start() cameraParent = new GameObject("StationaryCameraParent"); deathCam = new GameObject("DeathCam"); + CheckForBDA(); if (FlightGlobals.ActiveVessel != null) { cameraParent.transform.position = FlightGlobals.ActiveVessel.transform.position; @@ -265,11 +273,13 @@ void Update() if (Input.GetKeyDown(revertKey)) { + autoEnableOverriden = true; temporaryRevert = false; RevertCamera(); } else if (Input.GetKeyDown(cameraKey)) { + autoEnableOverriden = false; temporaryRevert = true; cameraActivate(); } @@ -334,6 +344,10 @@ void FixedUpdate() lastVesselPosition = vessel.transform.position; } + if (!cameraToolActive && autoEnableForBDA && !autoEnableOverriden) + { + AutoEnableForBDA(); + } if (cameraToolActive) { switch (toolMode) @@ -366,7 +380,7 @@ void FixedUpdate() dogfightTarget = null; if (cameraToolActive) { - Debug.Log("[CameraTools]: Reverting Because dogfightTarget is null"); + Debug.Log("[CameraTools]: Reverting because dogfightTarget is null"); RevertCamera(); } } @@ -799,7 +813,7 @@ void UpdateStationaryCamera() if (posCounter < 3) { posCounter++; - Debug.Log("[CameraTools]: flightCamera position: " + flightCamera.transform.position); + // Debug.Log("[CameraTools]: flightCamera position: " + flightCamera.transform.position); flightCamera.transform.position = resetPositionFix; if (hasSavedRotation) { @@ -1627,6 +1641,8 @@ void GuiWindow(int windowID) GUI.Label(new Rect(leftIndent + contentWidth - 40, contentTop + ((line - 0.15f) * entryHeight), 40, entryHeight), zoomFactor.ToString("0.0") + "x", leftLabel); } line++; + autoEnableForBDA = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), autoEnableForBDA, "Auto-Enable for BDArmory"); + line++; if (toolMode != ToolModes.Pathing) { @@ -2164,14 +2180,14 @@ void ToggleGui() void EnableGui() { guiEnabled = true; - Debug.Log("[CameraTools]: Showing CamTools GUI"); + // Debug.Log("[CameraTools]: Showing CamTools GUI"); } void DisableGui() { guiEnabled = false; Save(); - Debug.Log("[CameraTools]: Hiding CamTools GUI"); + // Debug.Log("[CameraTools]: Hiding CamTools GUI"); } void Dummy() @@ -2399,6 +2415,90 @@ private Type WeaponManagerType() return null; } + private void CheckForBDA() + { + // This checks for the existence of a BDArmory assembly and picks out the BDACompetitionMode and VesselSpawner singletons. + int foundCount = 0; + foreach (var assy in AssemblyLoader.loadedAssemblies) + if (assy.assembly.FullName.Contains("BDArmory")) + { + foreach (var t in assy.assembly.GetTypes()) + { + if (t.Name == "BDACompetitionMode") + { + bdCompetitionType = t; + bdCompetitionInstance = FindObjectOfType(bdCompetitionType); + ++foundCount; + } + else if (t.Name == "VesselSpawner") + { + bdVesselSpawnerType = t; + bdVesselSpawnerInstance = FindObjectOfType(bdVesselSpawnerType); + ++foundCount; + } + if (foundCount == 2) + return; + } + } + } + + private void AutoEnableForBDA() + { + if (bdCompetitionType != null && bdCompetitionInstance != null && bdVesselSpawnerType != null && bdVesselSpawnerInstance != null) + { + try + { + foreach (var fieldInfo in bdVesselSpawnerType.GetFields(BindingFlags.Public | BindingFlags.Instance)) + if (fieldInfo != null && fieldInfo.Name == "vesselsSpawning" && (bool)fieldInfo.GetValue(bdVesselSpawnerInstance)) + { + if (autoEnableOverrideWhileSpawning) + { + return; // Still spawning. + } + else + { + Debug.Log("[CameraTools]: Deactivating CameraTools while spawning vessels."); + autoEnableOverrideWhileSpawning = true; + RevertCamera(); + return; + } + } + autoEnableOverrideWhileSpawning = false; + if (vessel != null && !vessel.LandedOrSplashed) return; // Don't activate for landed vessels. + foreach (var fieldInfo in bdCompetitionType.GetFields(BindingFlags.Public | BindingFlags.Instance)) + if (fieldInfo != null) + switch (fieldInfo.Name) + { + case "competitionStarting": + if ((bool)fieldInfo.GetValue(bdCompetitionInstance)) + { + Debug.Log("[CameraTools]: Activating CameraTools for BDArmory competition as competition is starting."); + cameraActivate(); + return; + } + break; + case "competitionIsActive": + if ((bool)fieldInfo.GetValue(bdCompetitionInstance) && !(toolMode == ToolModes.DogfightCamera && dogfightTarget == null)) // Don't activate dogfight mode without a target once the competition is active. + { + Debug.Log("[CameraTools]: Activating CameraTools for BDArmory competition as competition is active."); + cameraActivate(); + return; + } + break; + } + } + catch (Exception e) + { + Debug.Log("[CameraTools]: Checking competition state of BDArmory failed: " + e.Message); + bdCompetitionInstance = null; + bdCompetitionType = null; + bdVesselSpawnerInstance = null; + bdVesselSpawnerType = null; + CheckForBDA(); + } + } + } + private FieldInfo GetThreatField() { Type wmModType = WeaponManagerType(); diff --git a/CameraTools/bin/Release/CameraTools.dll b/CameraTools/bin/Release/CameraTools.dll deleted file mode 100644 index aa88c6be9ccaa2b33e00313204ffccb4a00422e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56320 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 Nv(r!e`hVfTzX3MMHhBO5 From 40ada9435976c2cbeeb7136f6627ded49109fa5a Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 22 Nov 2020 17:13:46 +0100 Subject: [PATCH 020/263] Fix the parity of some checks and store the FieldInfo entries instead of looking for them each time. --- CameraTools/CamTools.cs | 120 ++++++++++++++++++++++++---------------- 1 file changed, 73 insertions(+), 47 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 1a0de2ae..57580302 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -29,8 +29,11 @@ public class CamTools : MonoBehaviour private bool autoEnableOverrideWhileSpawning = false; Type bdCompetitionType = null; object bdCompetitionInstance = null; + FieldInfo bdCompetitionStartingField = null; + FieldInfo bdCompetitionIsActiveField = null; Type bdVesselSpawnerType = null; object bdVesselSpawnerInstance = null; + FieldInfo bdVesselsSpawningField = null; #region Input [CTPersistantField] public string cameraKey = "home"; @@ -344,7 +347,7 @@ void FixedUpdate() lastVesselPosition = vessel.transform.position; } - if (!cameraToolActive && autoEnableForBDA && !autoEnableOverriden) + if (autoEnableForBDA && !autoEnableOverriden) { AutoEnableForBDA(); } @@ -2420,26 +2423,53 @@ private void CheckForBDA() // This checks for the existence of a BDArmory assembly and picks out the BDACompetitionMode and VesselSpawner singletons. int foundCount = 0; foreach (var assy in AssemblyLoader.loadedAssemblies) + { if (assy.assembly.FullName.Contains("BDArmory")) { foreach (var t in assy.assembly.GetTypes()) { - if (t.Name == "BDACompetitionMode") - { - bdCompetitionType = t; - bdCompetitionInstance = FindObjectOfType(bdCompetitionType); - ++foundCount; - } - else if (t.Name == "VesselSpawner") + if (t != null) { - bdVesselSpawnerType = t; - bdVesselSpawnerInstance = FindObjectOfType(bdVesselSpawnerType); - ++foundCount; + switch (t.Name) + { + case "BDACompetitionMode": + bdCompetitionType = t; + bdCompetitionInstance = FindObjectOfType(bdCompetitionType); + foreach (var fieldInfo in bdCompetitionType.GetFields(BindingFlags.Public | BindingFlags.Instance)) + if (fieldInfo != null) + { + switch (fieldInfo.Name) + { + case "competitionStarting": + bdCompetitionStartingField = fieldInfo; + ++foundCount; + break; + case "competitionIsActive": + bdCompetitionIsActiveField = fieldInfo; + ++foundCount; + break; + default: + break; + } + } + break; + case "VesselSpawner": + bdVesselSpawnerType = t; + bdVesselSpawnerInstance = FindObjectOfType(bdVesselSpawnerType); + foreach (var fieldInfo in bdVesselSpawnerType.GetFields(BindingFlags.Public | BindingFlags.Instance)) + if (fieldInfo != null && fieldInfo.Name == "vesselsSpawning") + bdVesselsSpawningField = fieldInfo; + ++foundCount; + break; + default: + break; + } } - if (foundCount == 2) + if (foundCount == 3) return; } } + } } private void AutoEnableForBDA() @@ -2448,50 +2478,46 @@ private void AutoEnableForBDA() { try { - foreach (var fieldInfo in bdVesselSpawnerType.GetFields(BindingFlags.Public | BindingFlags.Instance)) - if (fieldInfo != null && fieldInfo.Name == "vesselsSpawning" && (bool)fieldInfo.GetValue(bdVesselSpawnerInstance)) + if ((bool)bdVesselsSpawningField.GetValue(bdVesselSpawnerInstance)) + { + if (autoEnableOverrideWhileSpawning) { - if (autoEnableOverrideWhileSpawning) - { - return; // Still spawning. - } - else - { - Debug.Log("[CameraTools]: Deactivating CameraTools while spawning vessels."); - autoEnableOverrideWhileSpawning = true; - RevertCamera(); - return; - } + return; // Still spawning. } + else + { + Debug.Log("[CameraTools]: Deactivating CameraTools while spawning vessels."); + autoEnableOverrideWhileSpawning = true; + RevertCamera(); + return; + } + } autoEnableOverrideWhileSpawning = false; - if (vessel != null && !vessel.LandedOrSplashed) return; // Don't activate for landed vessels. - foreach (var fieldInfo in bdCompetitionType.GetFields(BindingFlags.Public | BindingFlags.Instance)) - if (fieldInfo != null) - switch (fieldInfo.Name) - { - case "competitionStarting": - if ((bool)fieldInfo.GetValue(bdCompetitionInstance)) - { - Debug.Log("[CameraTools]: Activating CameraTools for BDArmory competition as competition is starting."); - cameraActivate(); - return; - } - break; - case "competitionIsActive": - if ((bool)fieldInfo.GetValue(bdCompetitionInstance) && !(toolMode == ToolModes.DogfightCamera && dogfightTarget == null)) // Don't activate dogfight mode without a target once the competition is active. - { - Debug.Log("[CameraTools]: Activating CameraTools for BDArmory competition as competition is active."); - cameraActivate(); - return; - } - break; - } + + if (cameraToolActive) return; // It's already active. + + if (vessel == null || vessel.LandedOrSplashed) return; // Don't activate for landed/splashed vessels. + if ((bool)bdCompetitionStartingField.GetValue(bdCompetitionInstance)) + { + Debug.Log("[CameraTools]: Activating CameraTools for BDArmory competition as competition is starting."); + cameraActivate(); + return; + } + else if ((bool)bdCompetitionIsActiveField.GetValue(bdCompetitionInstance) && !(toolMode == ToolModes.DogfightCamera && dogfightTarget == null)) // Don't activate dogfight mode without a target once the competition is active. + { + Debug.Log("[CameraTools]: Activating CameraTools for BDArmory competition as competition is active."); + cameraActivate(); + return; + } } catch (Exception e) { Debug.Log("[CameraTools]: Checking competition state of BDArmory failed: " + e.Message); + bdCompetitionIsActiveField = null; + bdCompetitionStartingField = null; bdCompetitionInstance = null; bdCompetitionType = null; + bdVesselsSpawningField = null; bdVesselSpawnerInstance = null; bdVesselSpawnerType = null; CheckForBDA(); From edcfa3e15311d16573a1d02e68d075454315692f Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 22 Nov 2020 19:29:45 +0100 Subject: [PATCH 021/263] Adjust position of auto-enable button in settings. --- CameraTools/CamTools.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 57580302..b7ba2429 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -1629,6 +1629,8 @@ void GuiWindow(int windowID) } line++; line++; + autoEnableForBDA = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), autoEnableForBDA, "Auto-Enable for BDArmory"); + line++; if (autoFOV) { GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth / 2, entryHeight), "Autozoom Margin: "); @@ -1644,8 +1646,6 @@ void GuiWindow(int windowID) GUI.Label(new Rect(leftIndent + contentWidth - 40, contentTop + ((line - 0.15f) * entryHeight), 40, entryHeight), zoomFactor.ToString("0.0") + "x", leftLabel); } line++; - autoEnableForBDA = GUI.Toggle(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), autoEnableForBDA, "Auto-Enable for BDArmory"); - line++; if (toolMode != ToolModes.Pathing) { From 40e70a362dc600a4e5f988523f514feac4ab0166 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 19 Feb 2021 17:47:50 +0100 Subject: [PATCH 022/263] Fix occassional NRE from other things crashing. --- CameraTools/CTPartAudioController.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/CameraTools/CTPartAudioController.cs b/CameraTools/CTPartAudioController.cs index 93fe5175..28c51bf6 100644 --- a/CameraTools/CTPartAudioController.cs +++ b/CameraTools/CTPartAudioController.cs @@ -27,7 +27,7 @@ void Awake() void Start() { - if(!audioSource) + if (!audioSource) { Destroy(this); return; @@ -38,18 +38,18 @@ void Start() origRolloffMode = audioSource.rolloffMode; audioSource.rolloffMode = AudioRolloffMode.Logarithmic; audioSource.spatialBlend = 1; - + } void FixedUpdate() { - if(!audioSource) + if (!audioSource) { Destroy(this); return; } - if(!part || !vessel) + if (!part || !vessel || !FlightCamera.fetch) { Destroy(this); return; @@ -66,25 +66,25 @@ void FixedUpdate() 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, 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)); + } void OnDestroy() { CamTools.OnResetCTools -= OnResetCTools; - + } void OnResetCTools() From ee7d4661508186d17101afa1a923d3552cb0e9cb Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 21 Feb 2021 00:45:54 +0100 Subject: [PATCH 023/263] Fix 'Only custom filters can be played.' warnings due to AudioSource having 'playOnAwake' enabled by default in Unity. --- CameraTools/CTAtmosphericAudioController.cs | 66 ++++++++++----------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/CameraTools/CTAtmosphericAudioController.cs b/CameraTools/CTAtmosphericAudioController.cs index 70fd86dd..7bdd0ccf 100644 --- a/CameraTools/CTAtmosphericAudioController.cs +++ b/CameraTools/CTAtmosphericAudioController.cs @@ -17,49 +17,53 @@ public class CTAtmosphericAudioController : MonoBehaviour void Awake() { vessel = GetComponent(); - windAudioSource = gameObject.AddComponent(); + + windAudioSource = new GameObject().AddComponent(); windAudioSource.minDistance = 10; windAudioSource.maxDistance = 10000; windAudioSource.dopplerLevel = .35f; windAudioSource.spatialBlend = 1; AudioClip windclip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/windloop"); - if(!windclip) + if (!windclip) { - Destroy (this); + Destroy(this); return; } windAudioSource.clip = windclip; + windAudioSource.transform.parent = vessel.transform; - windHowlAudioSource = gameObject.AddComponent(); + windHowlAudioSource = new GameObject().AddComponent(); windHowlAudioSource.minDistance = 10; windHowlAudioSource.maxDistance = 7000; windHowlAudioSource.dopplerLevel = .5f; windHowlAudioSource.pitch = 0.25f; windHowlAudioSource.clip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/windhowl"); windHowlAudioSource.spatialBlend = 1; + windHowlAudioSource.transform.parent = vessel.transform; - windTearAudioSource = gameObject.AddComponent(); + windTearAudioSource = new GameObject().AddComponent(); windTearAudioSource.minDistance = 10; windTearAudioSource.maxDistance = 5000; windTearAudioSource.dopplerLevel = 0.45f; windTearAudioSource.pitch = 0.65f; windTearAudioSource.clip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/windtear"); windTearAudioSource.spatialBlend = 1; + windTearAudioSource.transform.parent = vessel.transform; sonicBoomSource = new GameObject().AddComponent(); - sonicBoomSource.transform.parent = vessel.transform; 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.volume = Mathf.Clamp01(vessel.GetTotalMass() / 4f); sonicBoomSource.Stop(); sonicBoomSource.spatialBlend = 1; + sonicBoomSource.transform.parent = vessel.transform; 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 (vessel.srfSpeed / (angleToCam) < 3.67f) { playedBoom = true; } @@ -70,32 +74,32 @@ void Awake() void FixedUpdate() { - if(!vessel) + if (!vessel) { return; } - if(Time.timeScale > 0 && vessel.dynamicPressurekPa > 0) + if (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 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; - if(waveFrontFactor > 0) + waveFrontFactor = (srfSpeed / (angleToCam) < 3.67f) ? waveFrontFactor + ((srfSpeed / (float)CamTools.speedOfSound) * waveFrontFactor) : 0; + if (waveFrontFactor > 0) { - if(!playedBoom) + if (!playedBoom) { sonicBoomSource.transform.position = vessel.transform.position + (-vessel.srf_velocity); sonicBoomSource.PlayOneShot(sonicBoomSource.clip); @@ -107,7 +111,7 @@ void FixedUpdate() } } - else if(CamTools.speedOfSound / (angleToCam) < 3.67f) + else if (CamTools.speedOfSound / (angleToCam) < 3.67f) { playedBoom = true; } @@ -117,10 +121,10 @@ void FixedUpdate() float sqrAccel = (float)vessel.acceleration.sqrMagnitude; //windloop - if(!windAudioSource.isPlaying) + if (!windAudioSource.isPlaying) { windAudioSource.Play(); - //Debug.Log("[CameraTools]: vessel dynamic pressure: " + vessel.dynamicPressurekPa); + Debug.Log("[CameraTools]: vessel dynamic pressure: " + vessel.dynamicPressurekPa); } float pressureFactor = Mathf.Clamp01((float)vessel.dynamicPressurekPa / 50f); float massFactor = Mathf.Clamp01(vessel.GetTotalMass() / 60f); @@ -129,7 +133,7 @@ void FixedUpdate() //windhowl - if(!windHowlAudioSource.isPlaying) + if (!windHowlAudioSource.isPlaying) { windHowlAudioSource.Play(); } @@ -139,7 +143,7 @@ void FixedUpdate() windHowlAudioSource.maxDistance = Mathf.Clamp(lagAudioFactor * 2500, windTearAudioSource.minDistance, 16000); //windtear - if(!windTearAudioSource.isPlaying) + if (!windTearAudioSource.isPlaying) { windTearAudioSource.Play(); } @@ -150,21 +154,21 @@ void FixedUpdate() 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,19 +177,15 @@ void FixedUpdate() void OnDestroy() { - if(sonicBoomSource) - { - Destroy(sonicBoomSource.gameObject); - } + if (sonicBoomSource) Destroy(sonicBoomSource.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); - Destroy(this); } } From 5fe312440521a8b2e73d77797a215a5cb72feb8d Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Mon, 22 Feb 2021 19:42:28 +0100 Subject: [PATCH 024/263] Comment out debugging info. --- CameraTools/CTAtmosphericAudioController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CameraTools/CTAtmosphericAudioController.cs b/CameraTools/CTAtmosphericAudioController.cs index 7bdd0ccf..f72dfa5c 100644 --- a/CameraTools/CTAtmosphericAudioController.cs +++ b/CameraTools/CTAtmosphericAudioController.cs @@ -124,7 +124,7 @@ void FixedUpdate() if (!windAudioSource.isPlaying) { windAudioSource.Play(); - Debug.Log("[CameraTools]: vessel dynamic pressure: " + vessel.dynamicPressurekPa); + // Debug.Log("[CameraTools]: vessel dynamic pressure: " + vessel.dynamicPressurekPa); } float pressureFactor = Mathf.Clamp01((float)vessel.dynamicPressurekPa / 50f); float massFactor = Mathf.Clamp01(vessel.GetTotalMass() / 60f); From 8e18361c1daaba669780e7f87244676b5c839987 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 4 Mar 2021 21:18:23 +0100 Subject: [PATCH 025/263] Smoother transitions and avoid jitter when switching targets and vessels. --- CameraTools/CamTools.cs | 216 ++++++++++++++++++++++++---------------- 1 file changed, 128 insertions(+), 88 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index b7ba2429..7715d4bd 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -34,6 +34,8 @@ public class CamTools : MonoBehaviour Type bdVesselSpawnerType = null; object bdVesselSpawnerInstance = null; FieldInfo bdVesselsSpawningField = null; + [CTPersistantField] public bool DEBUG = false; + bool vesselSwitched = false; #region Input [CTPersistantField] public string cameraKey = "home"; @@ -98,6 +100,8 @@ public class CamTools : MonoBehaviour Vector3 resetPositionFix;//fixes position movement after setting and resetting camera public delegate void ResetCTools(); public static event ResetCTools OnResetCTools; + double noDogfightTargetDelay = 5; + double noDogfightTargetTimer = -1; #endregion #region Recording @@ -347,7 +351,7 @@ void FixedUpdate() lastVesselPosition = vessel.transform.position; } - if (autoEnableForBDA && !autoEnableOverriden) + if (autoEnableForBDA && !autoEnableOverriden && (toolMode != ToolModes.Pathing || (selectedPathIndex >= 0 && currentPath.keyframeCount > 0))) { AutoEnableForBDA(); } @@ -360,6 +364,15 @@ void FixedUpdate() break; case ToolModes.DogfightCamera: UpdateDogfightCamera(); + if (noDogfightTargetTimer < 0 && dogfightTarget && dogfightTarget.isActiveVessel) + { + dogfightTarget = null; + if (cameraToolActive) + { + Debug.Log("[CameraTools]: Reverting because dogfightTarget is null"); + RevertCamera(); + } + } break; case ToolModes.Pathing: UpdatePathingCam(); @@ -375,19 +388,6 @@ void FixedUpdate() zoomFactor = Mathf.Exp(zoomExp) / Mathf.Exp(1); } } - - if (toolMode == ToolModes.DogfightCamera) - { - if (dogfightTarget && dogfightTarget.isActiveVessel) - { - dogfightTarget = null; - if (cameraToolActive) - { - Debug.Log("[CameraTools]: Reverting because dogfightTarget is null"); - RevertCamera(); - } - } - } } void LateUpdate() @@ -403,46 +403,57 @@ void LateUpdate() flightCamera.transform.localPosition = Vector3.zero; flightCamera.transform.localRotation = Quaternion.identity; } - else + else if (!vesselSwitched) { deathCam.transform.position = flightCamera.transform.position; deathCam.transform.rotation = flightCamera.transform.rotation; } + if (vesselSwitched) // We perform this here instead of waiting for the next frame to avoid a flicker of the camera being switched. + { + vesselSwitched = false; + switch (toolMode) + { + case ToolModes.DogfightCamera: + if (hasBDAI && useBDAutoTarget) + { + Vessel newAITarget = GetAITargetedVessel(); + if (newAITarget) + { + dogfightTarget = newAITarget; + } + } + StartDogfightCamera(); + break; + case ToolModes.StationaryCamera: + StartStationaryCamera(); + break; + case ToolModes.Pathing: + StartPathingCam(); + PlayPathingCam(); + break; + } + } } private void cameraActivate() { + if (DEBUG) Debug.Log("[CameraTools]: Activating camera."); + if (!cameraToolActive) + { + SaveOriginalCamera(); + } + deathCam.transform.position = flightCamera.transform.position; + deathCam.transform.rotation = flightCamera.transform.rotation; if (toolMode == ToolModes.StationaryCamera) { - if (!cameraToolActive) - { - SaveOriginalCamera(); - StartStationaryCamera(); - } - else - { - //RevertCamera(); - StartStationaryCamera(); - } + StartStationaryCamera(); } else if (toolMode == ToolModes.DogfightCamera) { - if (!cameraToolActive) - { - SaveOriginalCamera(); - StartDogfightCamera(); - } - else - { - StartDogfightCamera(); - } + StartDogfightCamera(); } else if (toolMode == ToolModes.Pathing) { - if (!cameraToolActive) - { - SaveOriginalCamera(); - } StartPathingCam(); PlayPathingCam(); } @@ -456,6 +467,7 @@ void StartDogfightCamera() Debug.Log("[CameraTools]: No active vessel."); return; } + if (DEBUG) Debug.Log("[CameraTools]: Starting dogfight camera."); if (!dogfightTarget) { @@ -476,16 +488,22 @@ void StartDogfightCamera() dogfightPrevTarget = dogfightTarget; hasDied = false; + noDogfightTargetTimer = -1; vessel = FlightGlobals.ActiveVessel; cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.CoM).normalized; - cameraParent.transform.position = deathCam.transform.position; // First update the cameraParent to the last deathCam configuration - cameraParent.transform.rotation = deathCam.transform.rotation; - flightCamera.SetTargetNone(); - flightCamera.transform.parent = cameraParent.transform; - flightCamera.DeactivateUpdate(); - cameraParent.transform.position = vessel.transform.position; // Then adjust the flightCamera for the new parent. - flightCamera.transform.localRotation = Quaternion.identity; + if (flightCamera.transform.parent != cameraParent.transform) + { + if (DEBUG) Debug.Log("[CameraTools]: Resetting flightCamera parent."); + cameraParent.transform.position = deathCam.transform.position; // First update the cameraParent to the last deathCam configuration + cameraParent.transform.rotation = deathCam.transform.rotation; + flightCamera.SetTargetNone(); + flightCamera.transform.parent = cameraParent.transform; + flightCamera.DeactivateUpdate(); + cameraParent.transform.position = vessel.CoM; // Then adjust the flightCamera for the new parent. + flightCamera.transform.localPosition = cameraParent.transform.InverseTransformPoint(deathCam.transform.position); + flightCamera.transform.localRotation = Quaternion.identity; + } cameraToolActive = true; @@ -503,7 +521,7 @@ void UpdateDogfightCamera() { if (!vessel || (!dogfightTarget && !dogfightLastTarget && !dogfightVelocityChase)) { - Debug.Log("[CameraTools]: Reverting during UpdateDogfightCamera()"); + Debug.Log("[CameraTools]: Reverting during UpdateDogfightCamera"); RevertCamera(); return; } @@ -670,8 +688,23 @@ void UpdateDogfightCamera() if (dogfightTarget != dogfightPrevTarget) { - //RevertCamera(); - StartDogfightCamera(); + if (false && dogfightTarget == null) // WIP + { + if (noDogfightTargetTimer < 0) + { + noDogfightTargetTimer = Time.time; + Debug.Log("[CameraTools]: Dogfight target is null, waiting briefly before restarting dogfight camera."); + } + if (noDogfightTargetTimer > 0 && Time.time - noDogfightTargetTimer > noDogfightTargetDelay) + { + Debug.Log("[CameraTools]: Finished waiting. Re-starting dogfight camera."); + StartDogfightCamera(); + } + } + else + { + StartDogfightCamera(); + } } } #endregion @@ -681,6 +714,7 @@ void StartStationaryCamera() { if (FlightGlobals.ActiveVessel != null) { + if (DEBUG) Debug.Log("[CameraTools]: Starting stationary camera."); hasDied = false; vessel = FlightGlobals.ActiveVessel; cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.GetWorldPos3D()).normalized; @@ -689,13 +723,18 @@ void StartStationaryCamera() cameraUp = Vector3.up; } - cameraParent.transform.position = deathCam.transform.position; // First update the cameraParent to the last deathCam configuration - cameraParent.transform.rotation = deathCam.transform.rotation; - flightCamera.SetTargetNone(); - flightCamera.transform.parent = cameraParent.transform; - flightCamera.DeactivateUpdate(); - cameraParent.transform.position = vessel.transform.position; // Then adjust the flightCamera for the new parent. - flightCamera.transform.localRotation = Quaternion.identity; + if (flightCamera.transform.parent != cameraParent.transform) + { + if (DEBUG) Debug.Log("[CameraTools]: Resetting flightCamera parent."); + cameraParent.transform.position = deathCam.transform.position; // First update the cameraParent to the last deathCam configuration + cameraParent.transform.rotation = deathCam.transform.rotation; + flightCamera.SetTargetNone(); + flightCamera.transform.parent = cameraParent.transform; + flightCamera.DeactivateUpdate(); + cameraParent.transform.position = vessel.CoM; // Then adjust the flightCamera for the new parent. + flightCamera.transform.localPosition = cameraParent.transform.InverseTransformPoint(deathCam.transform.position); + flightCamera.transform.localRotation = Quaternion.identity; + } manualPosition = Vector3.zero; if (randomMode) @@ -796,7 +835,6 @@ void StartStationaryCamera() SetDoppler(true); AddAtmoAudioControllers(true); - } else { @@ -981,6 +1019,13 @@ void UpdateStationaryCamera() #region Pathing Camera void StartPathingCam() { + if (DEBUG) Debug.Log("[CameraTools]: Starting pathing camera."); + if (selectedPathIndex < 0 || currentPath.keyframeCount <= 0) + { + if (DEBUG) Debug.Log("[CameraTools]: Unable to start pathing camera due to no valid paths."); + RevertCamera(); + return; + } 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)) @@ -1198,14 +1243,17 @@ void ViewKeyframe(int index) void PlayPathingCam() { + if (DEBUG) Debug.Log("[CameraTools]: Playing pathing camera."); 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; } @@ -1397,6 +1445,12 @@ void ResetDoppler() #region Revert/Reset void SwitchToVessel(Vessel v) { + if (v == null) + { + RevertCamera(); + return; + } + if (DEBUG) Debug.Log("[CameraTools]: Switching to vessel " + v.vesselName); vessel = v; CheckForBDAI(v); @@ -1456,31 +1510,13 @@ void SwitchToVessel(Vessel v) } } - if (toolMode == ToolModes.DogfightCamera) - { - StartCoroutine(ResetDogfightCamRoutine()); - } + vesselSwitched = true; } } - IEnumerator ResetDogfightCamRoutine() - { - yield return new WaitForEndOfFrame(); - - RevertCamera(); - if (hasBDAI && useBDAutoTarget) - { - Vessel newAITarget = GetAITargetedVessel(); - if (newAITarget) - { - dogfightTarget = newAITarget; - } - } - StartDogfightCamera(); - } - public void RevertCamera() { + if (DEBUG) Debug.Log("[CameraTools]: Reverting camera."); posCounter = 0; if (cameraToolActive) @@ -1501,9 +1537,12 @@ public void RevertCamera() { flightCamera.SetTarget(FlightGlobals.ActiveVessel.transform, FlightCamera.TargetMode.Vessel); } - flightCamera.transform.parent = origParent; - flightCamera.transform.position = origPosition; - flightCamera.transform.rotation = origRotation; + if (cameraToolActive) + { + flightCamera.transform.parent = origParent; + flightCamera.transform.position = origPosition; + flightCamera.transform.rotation = origRotation; + } if (HighLogic.LoadedSceneIsFlight) flightCamera.mainCamera.nearClipPlane = origNearClip; else @@ -1616,16 +1655,15 @@ void GuiWindow(int windowID) //tool mode switcher GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), contentWidth, entryHeight), "Tool: " + toolMode.ToString(), leftLabelBold); line++; - if (!cameraToolActive) + if (GUI.Button(new Rect(leftIndent, contentTop + (line * entryHeight), 25, entryHeight - 2), "<")) { - if (GUI.Button(new Rect(leftIndent, contentTop + (line * entryHeight), 25, entryHeight - 2), "<")) - { - CycleToolMode(false); - } - if (GUI.Button(new Rect(leftIndent + 25 + 4, contentTop + (line * entryHeight), 25, entryHeight - 2), ">")) - { - CycleToolMode(true); - } + CycleToolMode(false); + cameraActivate(); + } + if (GUI.Button(new Rect(leftIndent + 25 + 4, contentTop + (line * entryHeight), 25, entryHeight - 2), ">")) + { + CycleToolMode(true); + cameraActivate(); } line++; line++; @@ -2245,6 +2283,7 @@ void CurrentVesselWillDestroy(Vessel v) hasDied = true; diedTime = Time.time; + if (DEBUG) Debug.Log("[CameraTools]: Activating death camera."); // Something borks the camera position/rotation when the target/parent is set to none/null. This fixes that. deathCamVelocity = (vessel.radarAltitude > 10d ? vessel.srf_velocity : Vector3d.zero) / 2f; // Track the explosion a bit. flightCamera.SetTargetNone(); @@ -2655,6 +2694,7 @@ void Load() { availablePaths.Add(CameraPath.Load(node)); } + if (DEBUG) Debug.Log("[CameraTools]: Verbose debugging enabled."); } #endregion } From c84ca0dd25c3a6dbe68b3290b83f9f2b924992a1 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 4 Mar 2021 22:57:48 +0100 Subject: [PATCH 026/263] Remove noDogfightTargetTimer as it doesn't seem to trigger now. --- CameraTools/CamTools.cs | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 7715d4bd..89a6e5b6 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -100,8 +100,6 @@ public class CamTools : MonoBehaviour Vector3 resetPositionFix;//fixes position movement after setting and resetting camera public delegate void ResetCTools(); public static event ResetCTools OnResetCTools; - double noDogfightTargetDelay = 5; - double noDogfightTargetTimer = -1; #endregion #region Recording @@ -364,7 +362,7 @@ void FixedUpdate() break; case ToolModes.DogfightCamera: UpdateDogfightCamera(); - if (noDogfightTargetTimer < 0 && dogfightTarget && dogfightTarget.isActiveVessel) + if (dogfightTarget && dogfightTarget.isActiveVessel) { dogfightTarget = null; if (cameraToolActive) @@ -488,7 +486,6 @@ void StartDogfightCamera() dogfightPrevTarget = dogfightTarget; hasDied = false; - noDogfightTargetTimer = -1; vessel = FlightGlobals.ActiveVessel; cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.CoM).normalized; @@ -688,23 +685,7 @@ void UpdateDogfightCamera() if (dogfightTarget != dogfightPrevTarget) { - if (false && dogfightTarget == null) // WIP - { - if (noDogfightTargetTimer < 0) - { - noDogfightTargetTimer = Time.time; - Debug.Log("[CameraTools]: Dogfight target is null, waiting briefly before restarting dogfight camera."); - } - if (noDogfightTargetTimer > 0 && Time.time - noDogfightTargetTimer > noDogfightTargetDelay) - { - Debug.Log("[CameraTools]: Finished waiting. Re-starting dogfight camera."); - StartDogfightCamera(); - } - } - else - { - StartDogfightCamera(); - } + StartDogfightCamera(); } } #endregion From f9e33db301cb4a58dfac99995d0da51732b817dc Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Wed, 10 Mar 2021 21:23:45 +0100 Subject: [PATCH 027/263] Reduce flickering by compensating for floating origin changes. Add on-screen debugging. --- CameraTools/CamTools.cs | 127 ++++++++++++++++++++++++++++++---------- 1 file changed, 96 insertions(+), 31 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 89a6e5b6..6bed691e 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -4,6 +4,8 @@ using UnityEngine; using System.Reflection; using KSP.UI.Screens; +using System.Linq; + namespace CameraTools { [KSPAddon(KSPAddon.Startup.Flight, false)] @@ -35,6 +37,7 @@ public class CamTools : MonoBehaviour object bdVesselSpawnerInstance = null; FieldInfo bdVesselsSpawningField = null; [CTPersistantField] public bool DEBUG = false; + string message; bool vesselSwitched = false; #region Input @@ -76,6 +79,13 @@ public class CamTools : MonoBehaviour string guiOffsetUp = "5"; [CTPersistantField] public bool useOrbital = false; [CTPersistantField] public bool targetCoM = false; + List> debugMessages = new List>(); + 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; + #endregion #region Revert/Reset @@ -84,7 +94,6 @@ public class CamTools : MonoBehaviour bool hasSavedRotation = false; Quaternion savedRotation; bool temporaryRevert = false; - Vector3 lastVesselPosition = Vector3.zero; Vector3 lastTargetPosition = Vector3.zero; bool hasTarget = false; bool hasDied = false; @@ -235,7 +244,6 @@ void Start() GameEvents.onShowUI.Add(GameUIEnable); //GameEvents.onGamePause.Add (PostDeathRevert); GameEvents.OnVesselRecoveryRequested.Add(PostDeathRevert); - GameEvents.onFloatingOriginShift.Add(OnFloatingOriginShift); GameEvents.onGameSceneLoadRequested.Add(PostDeathRevert); cameraParent = new GameObject("StationaryCameraParent"); @@ -259,6 +267,16 @@ void Start() bdWmUnderAttackField = GetUnderAttackField(); GameEvents.onVesselChange.Add(SwitchToVessel); GameEvents.onVesselWillDestroy.Add(CurrentVesselWillDestroy); + + cStyle = new GUIStyle(HighLogic.Skin.label); + cStyle.fontStyle = UnityEngine.FontStyle.Bold; + cStyle.fontSize = 22; + cStyle.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); } void OnDestroy() @@ -332,21 +350,27 @@ void Update() } } + Vector3 lastCamVesselΔ = Vector3.zero; void FixedUpdate() { // Note: we have to perform 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) return; - if (hasDied && cameraToolActive) return; // Do nothing until we have an active vessel. + flightCamera.transform.position -= FloatingOrigin.Offset; // This fixed the floating origin shifts. (Vessel positions are updated by KSP automatically, but not other position vectors.) + // I am uncertain whether there are any other Kraken velocity corrections that need to be applied. - if (FlightGlobals.ActiveVessel != null && (vessel == null || vessel != FlightGlobals.ActiveVessel)) + if (DEBUG && FloatingOrigin.fetch.offset.sqrMagnitude > 0.2f || Krakensbane.GetLastCorrection().sqrMagnitude > 100f) { - vessel = FlightGlobals.ActiveVessel; + var message = "FloatingOrigin offset Δ: " + FloatingOrigin.Offset.magnitude.ToString("0.00") + ", Krakensbane velocity Δ: " + Krakensbane.GetLastCorrection().magnitude.ToString("0.0") + ", " + (dogfightTarget != null) + ", " + dogfightLastTarget + ", " + dogfightVelocityChase + ", Δ: " + (vessel.CoM - flightCamera.transform.position).magnitude.ToString("0.00") + ", Δ': " + lastCamVesselΔ.magnitude.ToString("0.00"); + Debug.Log("[CameraTools]: " + message); + DebugLog(message); } - if (vessel != null) + if (hasDied && cameraToolActive) return; // Do nothing until we have an active vessel. + + if (FlightGlobals.ActiveVessel != null && (vessel == null || vessel != FlightGlobals.ActiveVessel)) { - lastVesselPosition = vessel.transform.position; + vessel = FlightGlobals.ActiveVessel; } if (autoEnableForBDA && !autoEnableOverriden && (toolMode != ToolModes.Pathing || (selectedPathIndex >= 0 && currentPath.keyframeCount > 0))) @@ -367,7 +391,7 @@ void FixedUpdate() dogfightTarget = null; if (cameraToolActive) { - Debug.Log("[CameraTools]: Reverting because dogfightTarget is null"); + if (DEBUG) Debug.Log("[CameraTools]: Reverting because dogfightTarget is null"); RevertCamera(); } } @@ -386,6 +410,7 @@ void FixedUpdate() zoomFactor = Mathf.Exp(zoomExp) / Mathf.Exp(1); } } + lastCamVesselΔ = vessel.CoM - flightCamera.transform.position; } void LateUpdate() @@ -435,7 +460,7 @@ void LateUpdate() private void cameraActivate() { - if (DEBUG) Debug.Log("[CameraTools]: Activating camera."); + if (DEBUG) { Debug.Log("[CameraTools]: Activating camera."); DebugLog("Activating camera"); } if (!cameraToolActive) { SaveOriginalCamera(); @@ -465,7 +490,7 @@ void StartDogfightCamera() Debug.Log("[CameraTools]: No active vessel."); return; } - if (DEBUG) Debug.Log("[CameraTools]: Starting dogfight camera."); + if (DEBUG) { Debug.Log("[CameraTools]: Starting dogfight camera."); DebugLog("Starting dogfight camera"); } if (!dogfightTarget) { @@ -491,7 +516,6 @@ void StartDogfightCamera() if (flightCamera.transform.parent != cameraParent.transform) { - if (DEBUG) Debug.Log("[CameraTools]: Resetting flightCamera parent."); cameraParent.transform.position = deathCam.transform.position; // First update the cameraParent to the last deathCam configuration cameraParent.transform.rotation = deathCam.transform.rotation; flightCamera.SetTargetNone(); @@ -518,7 +542,7 @@ void UpdateDogfightCamera() { if (!vessel || (!dogfightTarget && !dogfightLastTarget && !dogfightVelocityChase)) { - Debug.Log("[CameraTools]: Reverting during UpdateDogfightCamera"); + if (DEBUG) { Debug.Log("[CameraTools]: Reverting during UpdateDogfightCamera"); } RevertCamera(); return; } @@ -531,7 +555,8 @@ void UpdateDogfightCamera() } else if (dogfightLastTarget) { - dogfightLastTargetPosition += dogfightLastTargetVelocity * Time.fixedDeltaTime; + dogfightLastTargetVelocity += Krakensbane.GetLastCorrection(); + dogfightLastTargetPosition += dogfightLastTargetVelocity * Time.fixedDeltaTime - FloatingOrigin.Offset; } cameraParent.transform.position = vessel.CoM; @@ -677,6 +702,12 @@ void UpdateDogfightCamera() Vessel newAITarget = GetAITargetedVessel(); if (newAITarget) { + if (DEBUG && dogfightTarget != newAITarget) + { + message = "Switching dogfight target to " + newAITarget.vesselName + (dogfightTarget != null ? " from " + dogfightTarget.vesselName : ""); + Debug.Log("[CameraTools]: " + message); + DebugLog(message); + } dogfightTarget = newAITarget; } targetUpdateTime = Planetarium.GetUniversalTime(); @@ -695,7 +726,12 @@ void StartStationaryCamera() { if (FlightGlobals.ActiveVessel != null) { - if (DEBUG) Debug.Log("[CameraTools]: Starting stationary camera."); + if (DEBUG) + { + message = "Starting stationary camera."; + Debug.Log("[CameraTools]: " + message); + DebugLog(message); + } hasDied = false; vessel = FlightGlobals.ActiveVessel; cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.GetWorldPos3D()).normalized; @@ -706,7 +742,6 @@ void StartStationaryCamera() if (flightCamera.transform.parent != cameraParent.transform) { - if (DEBUG) Debug.Log("[CameraTools]: Resetting flightCamera parent."); cameraParent.transform.position = deathCam.transform.position; // First update the cameraParent to the last deathCam configuration cameraParent.transform.rotation = deathCam.transform.rotation; flightCamera.SetTargetNone(); @@ -1000,13 +1035,19 @@ void UpdateStationaryCamera() #region Pathing Camera void StartPathingCam() { - if (DEBUG) Debug.Log("[CameraTools]: Starting pathing camera."); if (selectedPathIndex < 0 || currentPath.keyframeCount <= 0) { if (DEBUG) Debug.Log("[CameraTools]: Unable to start pathing camera due to no valid paths."); RevertCamera(); return; } + if (DEBUG) + { + message = "Starting pathing camera."; + Debug.Log("[CameraTools]: " + message); + DebugLog(message); + } + 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)) @@ -1224,7 +1265,12 @@ void ViewKeyframe(int index) void PlayPathingCam() { - if (DEBUG) Debug.Log("[CameraTools]: Playing pathing camera."); + if (DEBUG) + { + message = "Playing pathing camera."; + Debug.Log("[CameraTools]: " + message); + DebugLog(message); + } if (selectedPathIndex < 0) { if (DEBUG) Debug.Log("[CameraTools]: selectedPathIndex < 0, reverting."); @@ -1431,7 +1477,12 @@ void SwitchToVessel(Vessel v) RevertCamera(); return; } - if (DEBUG) Debug.Log("[CameraTools]: Switching to vessel " + v.vesselName); + if (DEBUG) + { + message = "Switching to vessel " + v.vesselName; + Debug.Log("[CameraTools]: " + message); + DebugLog(message); + } vessel = v; CheckForBDAI(v); @@ -1497,7 +1548,12 @@ void SwitchToVessel(Vessel v) public void RevertCamera() { - if (DEBUG) Debug.Log("[CameraTools]: Reverting camera."); + if (DEBUG) + { + message = "Reverting camera."; + Debug.Log("[CameraTools]: " + message); + DebugLog(message); + } posCounter = 0; if (cameraToolActive) @@ -1604,6 +1660,20 @@ void OnGUI() 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); + } + } + } } void GuiWindow(int windowID) @@ -2264,7 +2334,12 @@ void CurrentVesselWillDestroy(Vessel v) hasDied = true; diedTime = Time.time; - if (DEBUG) Debug.Log("[CameraTools]: Activating death camera."); + if (DEBUG) + { + message = "Activating death camera."; + Debug.Log("[CameraTools]: " + message); + DebugLog(message); + } // Something borks the camera position/rotation when the target/parent is set to none/null. This fixes that. deathCamVelocity = (vessel.radarAltitude > 10d ? vessel.srf_velocity : Vector3d.zero) / 2f; // Track the explosion a bit. flightCamera.SetTargetNone(); @@ -2300,16 +2375,6 @@ Vector3 GetPosFromMouse() else return Vector3.zero; } - void OnFloatingOriginShift(Vector3d offset, Vector3d data1) - { - /* - Debug.LogWarning ("[CameraTools]: ======Floating origin shifted.======"); - Debug.LogWarning ("[CameraTools]: ======Passed offset: "+offset+"======"); - Debug.LogWarning ("[CameraTools]: ======FloatingOrigin offset: "+FloatingOrigin.fetch.offset+"======"); - Debug.LogWarning("[CameraTools]: ========Floating Origin threshold: "+FloatingOrigin.fetch.threshold+"=========="); - */ - } - void UpdateLoadedVessels() { if (loadedVessels == null) @@ -2675,7 +2740,7 @@ void Load() { availablePaths.Add(CameraPath.Load(node)); } - if (DEBUG) Debug.Log("[CameraTools]: Verbose debugging enabled."); + if (DEBUG) { Debug.Log("[CameraTools]: Verbose debugging enabled."); } } #endregion } From 37b32627c119fe169e7fed6fdd640b2c998dc82a Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Wed, 10 Mar 2021 23:56:51 +0100 Subject: [PATCH 028/263] Correct grouping of debugging checks. --- CameraTools/CamTools.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 6bed691e..d7cbf85c 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -359,7 +359,7 @@ void FixedUpdate() flightCamera.transform.position -= FloatingOrigin.Offset; // This fixed the floating origin shifts. (Vessel positions are updated by KSP automatically, but not other position vectors.) // I am uncertain whether there are any other Kraken velocity corrections that need to be applied. - if (DEBUG && FloatingOrigin.fetch.offset.sqrMagnitude > 0.2f || Krakensbane.GetLastCorrection().sqrMagnitude > 100f) + if (DEBUG && (FloatingOrigin.fetch.offset.sqrMagnitude > 0.2f || Krakensbane.GetLastCorrection().sqrMagnitude > 100f)) { var message = "FloatingOrigin offset Δ: " + FloatingOrigin.Offset.magnitude.ToString("0.00") + ", Krakensbane velocity Δ: " + Krakensbane.GetLastCorrection().magnitude.ToString("0.0") + ", " + (dogfightTarget != null) + ", " + dogfightLastTarget + ", " + dogfightVelocityChase + ", Δ: " + (vessel.CoM - flightCamera.transform.position).magnitude.ToString("0.00") + ", Δ': " + lastCamVesselΔ.magnitude.ToString("0.00"); Debug.Log("[CameraTools]: " + message); From 37693e2256b49564e0eed3e82fdcdcde967bc52d Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Wed, 10 Mar 2021 23:57:35 +0100 Subject: [PATCH 029/263] Fix NRE in atmospheric audio when vessel is destroyed. --- CameraTools/CTAtmosphericAudioController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CameraTools/CTAtmosphericAudioController.cs b/CameraTools/CTAtmosphericAudioController.cs index f72dfa5c..658710c8 100644 --- a/CameraTools/CTAtmosphericAudioController.cs +++ b/CameraTools/CTAtmosphericAudioController.cs @@ -74,7 +74,7 @@ void Awake() void FixedUpdate() { - if (!vessel) + if (!vessel || !vessel.loaded || !vessel.isActiveAndEnabled) { return; } From 8e065ac64ea4d16cb1de6d4257592adf84c61ea9 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 16 Mar 2021 15:16:34 +0100 Subject: [PATCH 030/263] Add roll parameter to dogfight camera. --- CameraTools/CamTools.cs | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index d7cbf85c..79cf39c3 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -135,11 +135,14 @@ public class CamTools : MonoBehaviour #region Dogfight Camera Fields Vessel dogfightPrevTarget; Vessel dogfightTarget; - [CTPersistantField] public float dogfightDistance = 30; - [CTPersistantField] public float dogfightOffsetX = 10; - [CTPersistantField] public float dogfightOffsetY = 4; + [CTPersistantField] public float dogfightDistance = 30f; + [CTPersistantField] public float dogfightOffsetX = 10f; + [CTPersistantField] public float dogfightOffsetY = 4f; float dogfightMaxOffset = 50; [CTPersistantField] public float dogfightLerp = 0.2f; + [CTPersistantField] public float dogfightRoll = 0f; + Quaternion dogfightCameraRoll = Quaternion.identity; + Vector3 dogfightCameraRollUp = Vector3.up; [CTPersistantField] public float autoZoomMargin = 20; List loadedVessels; bool showingVesselList = false; @@ -357,7 +360,7 @@ void FixedUpdate() if (!FlightGlobals.ready) return; flightCamera.transform.position -= FloatingOrigin.Offset; // This fixed the floating origin shifts. (Vessel positions are updated by KSP automatically, but not other position vectors.) - // I am uncertain whether there are any other Kraken velocity corrections that need to be applied. + // I am uncertain whether there are any other Kraken velocity corrections that need to be applied. if (DEBUG && (FloatingOrigin.fetch.offset.sqrMagnitude > 0.2f || Krakensbane.GetLastCorrection().sqrMagnitude > 100f)) { @@ -421,6 +424,7 @@ void LateUpdate() // Something else keeps trying to steal the camera after the vessel has died, so we need to keep overriding it. flightCamera.SetTargetNone(); deathCam.transform.position += deathCamVelocity * Time.deltaTime; + deathCamVelocity *= 0.95f; flightCamera.transform.parent = deathCam.transform; flightCamera.DeactivateUpdate(); flightCamera.transform.localPosition = Vector3.zero; @@ -573,15 +577,27 @@ void UpdateDogfightCamera() } } - Vector3 offsetDirection = Vector3.Cross(cameraUp, dogfightLastTargetPosition - vessel.CoM).normalized; - Vector3 camPos = vessel.CoM + ((vessel.CoM - dogfightLastTargetPosition).normalized * dogfightDistance) + (dogfightOffsetX * offsetDirection) + (dogfightOffsetY * cameraUp); + //roll + if (dogfightRoll > 0) + { + var vesselRollTarget = Quaternion.RotateTowards(Quaternion.identity, Quaternion.FromToRotation(cameraUp, -vessel.ReferenceTransform.forward), dogfightRoll * Vector3.Angle(cameraUp, -vessel.ReferenceTransform.forward)); + dogfightCameraRoll = Quaternion.Lerp(dogfightCameraRoll, vesselRollTarget, dogfightLerp); + dogfightCameraRollUp = dogfightCameraRoll * cameraUp; + } + else + { + dogfightCameraRollUp = cameraUp; + } + + Vector3 offsetDirection = Vector3.Cross(dogfightCameraRollUp, dogfightLastTargetPosition - vessel.CoM).normalized; + Vector3 camPos = vessel.CoM + ((vessel.CoM - dogfightLastTargetPosition).normalized * dogfightDistance) + (dogfightOffsetX * offsetDirection) + (dogfightOffsetY * dogfightCameraRollUp); Vector3 localCamPos = cameraParent.transform.InverseTransformPoint(camPos); flightCamera.transform.localPosition = Vector3.Lerp(flightCamera.transform.localPosition, localCamPos, dogfightLerp); //rotation - Quaternion vesselLook = Quaternion.LookRotation(vessel.CoM - flightCamera.transform.position, cameraUp); - Quaternion targetLook = Quaternion.LookRotation(dogfightLastTargetPosition - flightCamera.transform.position, cameraUp); + Quaternion vesselLook = Quaternion.LookRotation(vessel.CoM - flightCamera.transform.position, dogfightCameraRollUp); + Quaternion targetLook = Quaternion.LookRotation(dogfightLastTargetPosition - flightCamera.transform.position, dogfightCameraRollUp); Quaternion camRot = Quaternion.Lerp(vesselLook, targetLook, 0.5f); flightCamera.transform.rotation = Quaternion.Lerp(flightCamera.transform.rotation, camRot, dogfightLerp); @@ -1950,7 +1966,11 @@ void GuiWindow(int windowID) GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), 30, entryHeight), "Lerp: "); dogfightLerp = (int)GUI.HorizontalSlider(new Rect(leftIndent + 30, contentTop + (line * entryHeight) + 6, contentWidth - 60, entryHeight), dogfightLerp * 100f, 1f, 50f) / 100f; GUI.Label(new Rect(leftIndent + contentWidth - 25, contentTop + (line * entryHeight), 25, entryHeight), dogfightLerp.ToString("0.00")); - line += 1f; + line++; + GUI.Label(new Rect(leftIndent, contentTop + (line * entryHeight), 30, entryHeight), "Roll: "); + dogfightRoll = (int)GUI.HorizontalSlider(new Rect(leftIndent + 30, contentTop + (line * entryHeight) + 6, contentWidth - 60, entryHeight), dogfightRoll * 20f, 0f, 20f) / 20f; + GUI.Label(new Rect(leftIndent + contentWidth - 25, contentTop + (line * entryHeight), 25, entryHeight), dogfightRoll.ToString("0.00")); + line++; } else if (toolMode == ToolModes.Pathing) { From bf50d485e673fd0a5ac81e6c23fd872bab82238c Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 16 Mar 2021 15:33:09 +0100 Subject: [PATCH 031/263] Update changelog and bump version number. --- .../GameData/CameraTools/CameraTools.version | 14 +++++++------- .../GameData/CameraTools/Changelog.txt | 9 +++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index 13ea1933..838003bf 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, + "MINOR": 15, "PATCH": 0, "BUILD": 0 }, "KSP_VERSION": { "MAJOR": 1, - "MINOR": 9, - "PATCH": 0 + "MINOR": 11, + "PATCH": 1 }, "KSP_VERSION_MIN": { "MAJOR": 1, @@ -21,7 +21,7 @@ }, "KSP_VERSION_MAX": { "MAJOR": 1, - "MINOR": 9, + "MINOR": 11, "PATCH": 99 } } diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 60b71ed2..93b892ae 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,12 @@ +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 From 7a067d70c71b00e052ebc7825898a2eb3d7f9c47 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 25 Mar 2021 10:15:49 +0100 Subject: [PATCH 032/263] Fix missing config file load/save bug. --- CameraTools/CTPersistantFIeld.cs | 27 +++++++++++++++++++-------- CameraTools/CamTools.cs | 1 + 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CameraTools/CTPersistantFIeld.cs b/CameraTools/CTPersistantFIeld.cs index f2bdbd74..53a01501 100644 --- a/CameraTools/CTPersistantFIeld.cs +++ b/CameraTools/CTPersistantFIeld.cs @@ -12,6 +12,12 @@ public CTPersistantField() { } public static void Save() { ConfigNode fileNode = ConfigNode.Load(settingsURL); + if (fileNode == null) + fileNode = new ConfigNode(); + + if (!fileNode.HasNode("CToolsSettings")) + fileNode.AddNode("CToolsSettings"); + ConfigNode settings = fileNode.GetNode("CToolsSettings"); foreach (var field in typeof(CamTools).GetFields()) @@ -28,19 +34,24 @@ public static void Save() public static void Load() { ConfigNode fileNode = ConfigNode.Load(settingsURL); - ConfigNode settings = fileNode.GetNode("CToolsSettings"); + if (fileNode == null) return; // No config file. - foreach (var field in typeof(CamTools).GetFields()) + if (fileNode.HasNode("CToolsSettings")) { - if (field == null) continue; - if (!field.IsDefined(typeof(CTPersistantField), false)) continue; + ConfigNode settings = fileNode.GetNode("CToolsSettings"); - if (settings.HasValue(field.Name)) + foreach (var field in typeof(CamTools).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(CamTools.fetch, parsedValue); + } } } } diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 79cf39c3..ddbddd4c 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -286,6 +286,7 @@ void OnDestroy() { GameEvents.onVesselChange.Remove(SwitchToVessel); GameEvents.onVesselWillDestroy.Remove(CurrentVesselWillDestroy); + Save(); } void Update() From 7449ca255d883a13100f085bab82ee92eafe5f6b Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 25 Mar 2021 10:17:36 +0100 Subject: [PATCH 033/263] Update changelog and version number. --- .../Distribution/GameData/CameraTools/CameraTools.version | 4 ++-- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 5 +++++ CameraTools/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index 838003bf..10e3c750 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -6,13 +6,13 @@ "VERSION": { "MAJOR": 1, "MINOR": 15, - "PATCH": 0, + "PATCH": 1, "BUILD": 0 }, "KSP_VERSION": { "MAJOR": 1, "MINOR": 11, - "PATCH": 1 + "PATCH": 0 }, "KSP_VERSION_MIN": { "MAJOR": 1, diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 93b892ae..ea2e3d69 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,8 @@ +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. diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index 579bef46..d09734e7 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.14.0" )] -[assembly: AssemblyFileVersion( "1.14.0.0" )] +[assembly: AssemblyVersion( "1.15.1" )] +[assembly: AssemblyFileVersion( "1.15.1.0" )] From 28398c9905271ca15563b3c022dba1b89a497641 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 1 Apr 2021 23:41:25 +0200 Subject: [PATCH 034/263] Don't touch camera when we're not in control of it. Use TimeWarp instead of Time for velocity calculations. Allow switching into and out of IVA and Map modes without breaking. --- CameraTools/CamTools.cs | 139 +++++++++++++----- CameraTools/CameraTools.csproj | 10 +- .../GameData/CameraTools/CameraTools.version | 2 +- .../GameData/CameraTools/Changelog.txt | 8 +- CameraTools/Properties/AssemblyInfo.cs | 4 +- CameraTools/VesselExtensions.cs | 47 ++++++ 6 files changed, 162 insertions(+), 48 deletions(-) create mode 100644 CameraTools/VesselExtensions.cs diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index ddbddd4c..57377b34 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -1,10 +1,9 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using System.Reflection; using KSP.UI.Screens; +using System.Collections.Generic; using System.Linq; +using System.Reflection; +using System; +using UnityEngine; namespace CameraTools { @@ -43,6 +42,7 @@ public class CamTools : MonoBehaviour #region Input [CTPersistantField] public string cameraKey = "home"; [CTPersistantField] public string revertKey = "end"; + [CTPersistantField] public bool enableKeypad = false; string fmUpKey = "[7]"; string fmDownKey = "[1]"; string fmForwardKey = "[8]"; @@ -94,6 +94,7 @@ public class CamTools : MonoBehaviour bool hasSavedRotation = false; Quaternion savedRotation; bool temporaryRevert = false; + bool wasActiveBeforeModeChange = false; Vector3 lastTargetPosition = Vector3.zero; bool hasTarget = false; bool hasDied = false; @@ -177,7 +178,6 @@ public class CamTools : MonoBehaviour string guiKeyZoomSpeed = "1"; float zoomFactor = 1; [CTPersistantField] public float zoomExp = 1; - [CTPersistantField] public bool enableKeypad = false; [CTPersistantField] public float maxRelV = 2500; #endregion @@ -270,6 +270,7 @@ void Start() bdWmUnderAttackField = GetUnderAttackField(); GameEvents.onVesselChange.Add(SwitchToVessel); GameEvents.onVesselWillDestroy.Add(CurrentVesselWillDestroy); + GameEvents.OnCameraChange.Add(CameraModeChange); cStyle = new GUIStyle(HighLogic.Skin.label); cStyle.fontStyle = UnityEngine.FontStyle.Bold; @@ -286,9 +287,31 @@ void OnDestroy() { GameEvents.onVesselChange.Remove(SwitchToVessel); GameEvents.onVesselWillDestroy.Remove(CurrentVesselWillDestroy); + GameEvents.OnCameraChange.Remove(CameraModeChange); Save(); } + void CameraModeChange(CameraManager.CameraMode mode) + { + if (mode != CameraManager.CameraMode.Flight && CameraManager.Instance.previousCameraMode == CameraManager.CameraMode.Flight) + { + wasActiveBeforeModeChange = cameraToolActive; + cameraToolActive = false; + } + else if (mode == CameraManager.CameraMode.Flight) + { + if (wasActiveBeforeModeChange && !autoEnableOverriden) + { + Debug.Log("[CameraTools]: Camera mode changed to " + mode + ", reactivating " + toolMode + "."); + cameraToolActive = true; + RevertCamera(); + flightCamera.transform.position = deathCam.transform.position; + flightCamera.transform.rotation = deathCam.transform.rotation; + cameraActivate(); + } + } + } + void Update() { if (!isRecordingInput) @@ -359,15 +382,24 @@ void FixedUpdate() { // Note: we have to perform 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) return; + if (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Flight) return; - flightCamera.transform.position -= FloatingOrigin.Offset; // This fixed the floating origin shifts. (Vessel positions are updated by KSP automatically, but not other position vectors.) - // I am uncertain whether there are any other Kraken velocity corrections that need to be applied. - - if (DEBUG && (FloatingOrigin.fetch.offset.sqrMagnitude > 0.2f || Krakensbane.GetLastCorrection().sqrMagnitude > 100f)) + if (cameraToolActive) { - var message = "FloatingOrigin offset Δ: " + FloatingOrigin.Offset.magnitude.ToString("0.00") + ", Krakensbane velocity Δ: " + Krakensbane.GetLastCorrection().magnitude.ToString("0.0") + ", " + (dogfightTarget != null) + ", " + dogfightLastTarget + ", " + dogfightVelocityChase + ", Δ: " + (vessel.CoM - flightCamera.transform.position).magnitude.ToString("0.00") + ", Δ': " + lastCamVesselΔ.magnitude.ToString("0.00"); - Debug.Log("[CameraTools]: " + message); - DebugLog(message); + if ((!hasDied && flightCamera.transform.parent != cameraParent.transform) || (hasDied && flightCamera.transform.parent != deathCam.transform)) + { + message = "Someone has stolen the camera parent! Abort!"; + Debug.Log("[CameraTools]: " + message); DebugLog(message); + cameraToolActive = false; + RevertCamera(); + } + + // if (DEBUG && (FloatingOrigin.fetch.offset.sqrMagnitude > 10f || Krakensbane.GetLastCorrection().sqrMagnitude > 100f)) + // { + // var message = "FloatingOrigin offset Δ: " + FloatingOrigin.Offset.magnitude.ToString("0.00") + ", Krakensbane velocity Δ: " + Krakensbane.GetLastCorrection().magnitude.ToString("0.0") + ", " + (dogfightTarget != null) + ", " + dogfightLastTarget + ", " + dogfightVelocityChase + ", Δ: " + (vessel.CoM - flightCamera.transform.position).magnitude.ToString("0.00") + ", Δ': " + lastCamVesselΔ.magnitude.ToString("0.00"); + // Debug.Log("[CameraTools]: " + message); + // DebugLog(message); + // } } if (hasDied && cameraToolActive) return; // Do nothing until we have an active vessel. @@ -414,7 +446,7 @@ void FixedUpdate() zoomFactor = Mathf.Exp(zoomExp) / Mathf.Exp(1); } } - lastCamVesselΔ = vessel.CoM - flightCamera.transform.position; + if (DEBUG) lastCamVesselΔ = vessel.CoM - flightCamera.transform.position; } void LateUpdate() @@ -422,21 +454,33 @@ void LateUpdate() UpdateCameraShake(); // Update camera shake each frame so that it dies down. if (hasDied && cameraToolActive) { - // Something else keeps trying to steal the camera after the vessel has died, so we need to keep overriding it. - flightCamera.SetTargetNone(); - deathCam.transform.position += deathCamVelocity * Time.deltaTime; + deathCam.transform.position += deathCamVelocity * TimeWarp.deltaTime; deathCamVelocity *= 0.95f; - flightCamera.transform.parent = deathCam.transform; - flightCamera.DeactivateUpdate(); - flightCamera.transform.localPosition = Vector3.zero; - flightCamera.transform.localRotation = Quaternion.identity; + 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. + { + flightCamera.SetTargetNone(); + flightCamera.transform.parent = deathCam.transform; + flightCamera.DeactivateUpdate(); + flightCamera.transform.localPosition = Vector3.zero; + flightCamera.transform.localRotation = Quaternion.identity; + } } else if (!vesselSwitched) { - deathCam.transform.position = flightCamera.transform.position; - deathCam.transform.rotation = flightCamera.transform.rotation; + switch (CameraManager.Instance.currentCameraMode) + { + case CameraManager.CameraMode.IVA: + var IVACamera = CameraManager.GetCurrentCamera(); + deathCam.transform.position = IVACamera.transform.position; + deathCam.transform.rotation = IVACamera.transform.rotation; + break; + case CameraManager.CameraMode.Flight: + deathCam.transform.position = flightCamera.transform.position; + deathCam.transform.rotation = flightCamera.transform.rotation; + break; + } } - if (vesselSwitched) // We perform this here instead of waiting for the next frame to avoid a flicker of the camera being switched. + if (cameraToolActive && vesselSwitched) // We perform this here instead of waiting for the next frame to avoid a flicker of the camera being switched. { vesselSwitched = false; switch (toolMode) @@ -517,7 +561,7 @@ void StartDogfightCamera() hasDied = false; vessel = FlightGlobals.ActiveVessel; - cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.CoM).normalized; + cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.CoM - FloatingOrigin.Offset).normalized; if (flightCamera.transform.parent != cameraParent.transform) { @@ -526,7 +570,7 @@ void StartDogfightCamera() flightCamera.SetTargetNone(); flightCamera.transform.parent = cameraParent.transform; flightCamera.DeactivateUpdate(); - cameraParent.transform.position = vessel.CoM; // Then adjust the flightCamera for the new parent. + cameraParent.transform.position = vessel.CoM - FloatingOrigin.Offset; // Then adjust the flightCamera for the new parent. flightCamera.transform.localPosition = cameraParent.transform.InverseTransformPoint(deathCam.transform.position); flightCamera.transform.localRotation = Quaternion.identity; } @@ -543,6 +587,8 @@ void StartDogfightCamera() AddAtmoAudioControllers(false); } + Vector3 debugCameraPos = Vector3.zero; + Quaternion debugCameraRot = Quaternion.identity; void UpdateDogfightCamera() { if (!vessel || (!dogfightTarget && !dogfightLastTarget && !dogfightVelocityChase)) @@ -552,29 +598,30 @@ void UpdateDogfightCamera() return; } + var vesselCoM = vessel.CoM - FloatingOrigin.Offset; // vessel.CoM hasn't been updated for floating origin shifts yet. if (dogfightTarget) { dogfightLastTarget = true; - dogfightLastTargetPosition = dogfightTarget.CoM; + dogfightLastTargetPosition = dogfightTarget.CoM - FloatingOrigin.Offset; dogfightLastTargetVelocity = dogfightTarget.rb_velocity; } else if (dogfightLastTarget) { dogfightLastTargetVelocity += Krakensbane.GetLastCorrection(); - dogfightLastTargetPosition += dogfightLastTargetVelocity * Time.fixedDeltaTime - FloatingOrigin.Offset; + dogfightLastTargetPosition += dogfightLastTargetVelocity * TimeWarp.fixedDeltaTime - FloatingOrigin.Offset; } - cameraParent.transform.position = vessel.CoM; + cameraParent.transform.position = vesselCoM; if (dogfightVelocityChase) { - if (vessel.srfSpeed > 1) + if (vessel.speed > 1) { - dogfightLastTargetPosition = vessel.CoM + (vessel.srf_velocity.normalized * 5000); + dogfightLastTargetPosition = vesselCoM + vessel.Velocity().normalized * 5000; } else { - dogfightLastTargetPosition = vessel.CoM + (vessel.ReferenceTransform.up * 5000); + dogfightLastTargetPosition = vesselCoM + vessel.ReferenceTransform.up * 5000; } } @@ -590,14 +637,23 @@ void UpdateDogfightCamera() dogfightCameraRollUp = cameraUp; } - Vector3 offsetDirection = Vector3.Cross(dogfightCameraRollUp, dogfightLastTargetPosition - vessel.CoM).normalized; - Vector3 camPos = vessel.CoM + ((vessel.CoM - dogfightLastTargetPosition).normalized * dogfightDistance) + (dogfightOffsetX * offsetDirection) + (dogfightOffsetY * dogfightCameraRollUp); + Vector3 offsetDirection = Vector3.Cross(dogfightCameraRollUp, dogfightLastTargetPosition - vesselCoM).normalized; + Vector3 camPos = vesselCoM + ((vesselCoM - dogfightLastTargetPosition).normalized * dogfightDistance) + (dogfightOffsetX * offsetDirection) + (dogfightOffsetY * dogfightCameraRollUp); Vector3 localCamPos = cameraParent.transform.InverseTransformPoint(camPos); flightCamera.transform.localPosition = Vector3.Lerp(flightCamera.transform.localPosition, localCamPos, dogfightLerp); + // if (DEBUG) + // { + // debugMessages.Clear(); + // DebugLog("localCamPos: " + localCamPos.ToString("0.0") + ", " + flightCamera.transform.localPosition.ToString("0.0")); + // DebugLog("offset from vessel CoM: " + (flightCamera.transform.position - vesselCoM).ToString("0.0")); + // DebugLog("camPos: " + camPos.ToString("0.0") + ", floating origin offset: " + FloatingOrigin.Offset.ToString("0.0")); + // DebugLog("camPos - vesselCoM: " + (camPos - vesselCoM).ToString("0.0")); + // DebugLog("vel: " + vessel.Velocity().ToString("0.0") + ", Kraken velocity: " + Krakensbane.GetFrameVelocity().ToString("0.0") + ", ΔKv: " + Krakensbane.GetLastCorrection().ToString("0.0")); + // } //rotation - Quaternion vesselLook = Quaternion.LookRotation(vessel.CoM - flightCamera.transform.position, dogfightCameraRollUp); + Quaternion vesselLook = Quaternion.LookRotation(vesselCoM - flightCamera.transform.position, dogfightCameraRollUp); Quaternion targetLook = Quaternion.LookRotation(dogfightLastTargetPosition - flightCamera.transform.position, dogfightCameraRollUp); Quaternion camRot = Quaternion.Lerp(vesselLook, targetLook, 0.5f); flightCamera.transform.rotation = Quaternion.Lerp(flightCamera.transform.rotation, camRot, dogfightLerp); @@ -612,7 +668,7 @@ void UpdateDogfightCamera() } else { - float angle = Vector3.Angle(dogfightLastTargetPosition - flightCamera.transform.position, vessel.CoM - flightCamera.transform.position); + float angle = Vector3.Angle(dogfightLastTargetPosition - flightCamera.transform.position, vesselCoM - flightCamera.transform.position); targetFoV = Mathf.Clamp(angle + autoZoomMargin, 0.1f, 60f); } manualFOV = targetFoV; @@ -918,11 +974,11 @@ void UpdateStationaryCamera() if (referenceMode == ReferenceModes.Surface) { - flightCamera.transform.position -= Time.fixedDeltaTime * Mathf.Clamp((float)vessel.srf_velocity.magnitude, 0, maxRelV) * vessel.srf_velocity.normalized; + flightCamera.transform.position -= TimeWarp.fixedDeltaTime * Mathf.Clamp((float)vessel.srf_velocity.magnitude, 0, maxRelV) * vessel.srf_velocity.normalized; } else if (referenceMode == ReferenceModes.Orbit) { - flightCamera.transform.position -= Time.fixedDeltaTime * Mathf.Clamp((float)vessel.obt_velocity.magnitude, 0, maxRelV) * vessel.obt_velocity.normalized; + flightCamera.transform.position -= TimeWarp.fixedDeltaTime * Mathf.Clamp((float)vessel.obt_velocity.magnitude, 0, maxRelV) * vessel.obt_velocity.normalized; } else if (referenceMode == ReferenceModes.InitialVelocity) { @@ -935,7 +991,7 @@ void UpdateStationaryCamera() { camVelocity = (initialVelocity - vessel.srf_velocity); } - flightCamera.transform.position += camVelocity * Time.fixedDeltaTime; + flightCamera.transform.position += camVelocity * TimeWarp.fixedDeltaTime; } } @@ -1501,6 +1557,11 @@ void SwitchToVessel(Vessel v) DebugLog(message); } vessel = v; + // Switch to a usable camera mode if necessary. + if (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA) + { + CameraManager.Instance.SetCameraFlight(); + } CheckForBDAI(v); // reactivate camera if it was reverted diff --git a/CameraTools/CameraTools.csproj b/CameraTools/CameraTools.csproj index 31bdcabc..53d7eb32 100644 --- a/CameraTools/CameraTools.csproj +++ b/CameraTools/CameraTools.csproj @@ -62,18 +62,19 @@ - - - - + + + + + @@ -316,7 +317,6 @@ - false bin\Release\ AnyCPU prompt - true false portable @@ -78,6 +68,9 @@ + + + diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index e8f54c60..0c8bf394 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -5,7 +5,7 @@ "CHANGE_LOG_URL": "https://github.com/BrettRyland/CameraTools/blob/master/CameraTools/Distribution/GameData/CameraTools/Changelog.txt", "VERSION": { "MAJOR": 1, - "MINOR": 22, + "MINOR": 23, "PATCH": 0, "BUILD": 0 }, diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 3d89c3bc..18bc4ce5 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,5 +1,7 @@ +v1.23.0 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. v1.22.0 Bugfixes: diff --git a/CameraTools/Integration/BDArmory.cs b/CameraTools/Integration/BDArmory.cs new file mode 100644 index 00000000..73c4cbd8 --- /dev/null +++ b/CameraTools/Integration/BDArmory.cs @@ -0,0 +1,529 @@ +using System.Collections.Generic; +using System.Linq; +using System.IO; +using System.Reflection; +using System; +using UnityEngine; + + +namespace CameraTools.Integration +{ + [KSPAddon(KSPAddon.Startup.Flight, false)] + public class BDArmory : MonoBehaviour + { + #region Public fields + public static BDArmory instance; + public bool hasBDA = false; + + [CTPersistantField] public bool autoEnableForBDA = false; + [CTPersistantField] public bool useBDAutoTarget = false; + [CTPersistantField] public bool autoTargetIncomingMissiles = true; + [CTPersistantField] public bool useCentroid = false; + public bool autoEnableOverride = false; + public bool hasBDAI = false; + public bool hasPilotAI = false; + public List bdWMVessels + { + get + { + if (hasBDA && (_bdWMVessels is null || Time.time - _bdWMVesselsLastUpdate > 1f)) GetBDVessels(); // Update once per second. + return _bdWMVessels; + } + } + #endregion + + #region Private fields + CamTools camTools => CamTools.fetch; + Vessel vessel => CamTools.fetch.vessel; + object bdCompetitionInstance = null; + Func bdCompetitionStartingFieldGetter = null; + Func bdCompetitionIsActiveFieldGetter = null; + object bdVesselSpawnerInstance = null; + Func bdVesselsSpawningFieldGetter = null; + Func bdVesselsSpawningPropertyGetter = null; + Type bdBDATournamentType = null; + object bdBDATournamentInstance = null; + Func bdTournamentWarpInProgressFieldGetter = null; + bool hasBDWM = false; + object aiComponent = null; + object wmComponent = null; + Func bdAITargetFieldGetter = null; + Func bdWmThreatFieldGetter = null; + Func bdWmMissileFieldGetter = null; + Func bdWmUnderFireFieldGetter = null; + Func bdWmUnderAttackFieldGetter = null; + object bdLoadedVesselSwitcherInstance = null; + Func bdLoadedVesselSwitcherVesselsPropertyGetter = null; + Dictionary> bdActiveVessels = new Dictionary>(); + float AItargetUpdateTime = 0; + Vessel newAITarget = null; + List _bdWMVessels = new List(); + float _bdWMVesselsLastUpdate = 0; + #endregion + + void Awake() + { + if (instance is not null) Destroy(instance); + instance = this; + CTPersistantField.Load("BDArmoryIntegration", typeof(BDArmory), this); + } + + void Start() + { + CheckForBDA(); + if (hasBDA) + { + GetAITargetField(); + GetThreatField(); + GetMissileField(); + GetUnderFireField(); + GetUnderAttackField(); + if (FlightGlobals.ActiveVessel is not null) + { + CheckForBDAI(FlightGlobals.ActiveVessel); + CheckForBDWM(FlightGlobals.ActiveVessel); + } + } + } + + void OnDestroy() + { + CTPersistantField.Save("BDArmoryIntegration", typeof(BDArmory), this); + } + + void CheckForBDA() + { + // This checks for the existence of a BDArmory assembly and picks out the BDACompetitionMode and VesselSpawner singletons. + bdCompetitionInstance = null; + bdCompetitionIsActiveFieldGetter = null; + bdCompetitionStartingFieldGetter = null; + bdVesselSpawnerInstance = null; + bdVesselsSpawningFieldGetter = null; + bdVesselsSpawningPropertyGetter = null; + bdLoadedVesselSwitcherVesselsPropertyGetter = null; + bdBDATournamentType = null; + bdBDATournamentInstance = null; + foreach (var assy in AssemblyLoader.loadedAssemblies) + { + if (assy.assembly.FullName.Contains("BDArmory")) + { + hasBDA = true; + foreach (var t in assy.assembly.GetTypes()) + { + if (t != null) + { + switch (t.Name) + { + case "BDACompetitionMode": + bdCompetitionInstance = FindObjectOfType(t); + foreach (var fieldInfo in t.GetFields(BindingFlags.Public | BindingFlags.Instance)) + if (fieldInfo != null) + { + switch (fieldInfo.Name) + { + case "competitionStarting": + bdCompetitionStartingFieldGetter = ReflectionUtils.CreateGetter(fieldInfo); + break; + case "competitionIsActive": + bdCompetitionIsActiveFieldGetter = ReflectionUtils.CreateGetter(fieldInfo); + break; + default: + break; + } + } + break; + case "VesselSpawnerStatus": + foreach (var propertyInfo in t.GetProperties(BindingFlags.Public | BindingFlags.Static)) + if (propertyInfo != null && propertyInfo.Name == "inhibitCameraTools") + { + bdVesselsSpawningPropertyGetter = ReflectionUtils.BuildGetAccessor(propertyInfo.GetGetMethod()); + if (bdVesselsSpawningFieldGetter != null) // Clear the deprecated field. + { bdVesselsSpawningFieldGetter = null; } + break; + } + break; + case "LoadedVesselSwitcher": + bdLoadedVesselSwitcherInstance = FindObjectOfType(t); + foreach (var propertyInfo in t.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + if (propertyInfo != null && propertyInfo.Name == "Vessels") + { + bdLoadedVesselSwitcherVesselsPropertyGetter = ReflectionUtils.BuildGetAccessor(propertyInfo.GetGetMethod()); + break; + } + break; + case "VesselSpawner": + if (bdVesselsSpawningPropertyGetter == null) + { + if (!t.IsSubclassOf(typeof(UnityEngine.Object))) continue; // In BDArmory v1.5.0 and upwards, VesselSpawner is a static class. + bdVesselSpawnerInstance = FindObjectOfType(t); + foreach (var fieldInfo in t.GetFields(BindingFlags.Public | BindingFlags.Instance)) + if (fieldInfo != null && fieldInfo.Name == "vesselsSpawning") // deprecated in favour of VesselSpawnerStatus.inhibitCameraTools + { + bdVesselsSpawningFieldGetter = ReflectionUtils.CreateGetter(fieldInfo); + break; + } + } + break; + case "BDATournament": + bdBDATournamentType = t; + bdBDATournamentInstance = FindObjectOfType(bdBDATournamentType); + foreach (var fieldInfo in bdBDATournamentType.GetFields(BindingFlags.Public | BindingFlags.Instance)) + if (fieldInfo != null && fieldInfo.Name == "warpingInProgress") + { + bdTournamentWarpInProgressFieldGetter = ReflectionUtils.CreateGetter(fieldInfo); + break; + } + break; + default: + break; + } + } + } + } + } + } + + public void CheckForBDAI(Vessel v) + { + hasBDAI = false; + hasPilotAI = false; + aiComponent = null; + if (v) + { + foreach (Part p in v.parts) + { // We actually want BDGenericAIBase, but we can't use GetComponent(string) to find it, so we look for its subclasses. + if (p.GetComponent("BDModulePilotAI")) + { + hasBDAI = true; + hasPilotAI = true; + aiComponent = (object)p.GetComponent("BDModulePilotAI"); + return; + } + if (p.GetComponent("BDModuleVTOLAI")) + { + hasBDAI = true; + hasPilotAI = true; + aiComponent = (object)p.GetComponent("BDModuleVTOLAI"); + return; + } + if (p.GetComponent("BDModuleSurfaceAI")) + { + hasBDAI = true; + hasPilotAI = false; + aiComponent = (object)p.GetComponent("BDModuleSurfaceAI"); + return; + } + } + } + } + + public bool CheckForBDWM(Vessel v) + { + hasBDWM = false; + wmComponent = null; + if (v) + { + foreach (Part p in v.parts) + { + if (p.GetComponent("MissileFire")) + { + hasBDWM = true; + wmComponent = (object)p.GetComponent("MissileFire"); + return true; + } + } + } + return false; + } + + Vessel GetAITargetedVessel() + { + // Missiles are high priority. + if (autoTargetIncomingMissiles && hasBDWM && wmComponent != null && bdWmMissileFieldGetter != null) + { + var missile = bdWmMissileFieldGetter(wmComponent); // Priority 1: incoming missiles. + if (missile != null) return missile; + } + + // Don't update too often unless there's no target. + if (camTools.dogfightTarget != null && Time.time - AItargetUpdateTime < 3) return camTools.dogfightTarget; + + // Threats + if (hasBDWM && wmComponent != null && bdWmThreatFieldGetter != null) + { + bool underFire = bdWmUnderFireFieldGetter(wmComponent); // Getting attacked by guns. + bool underAttack = autoTargetIncomingMissiles && bdWmUnderAttackFieldGetter(wmComponent); // Getting attacked by guns or missiles. + + if (underFire || underAttack) + { + var threat = bdWmThreatFieldGetter(wmComponent); // Priority 2: incoming fire (can also be missiles). + if (threat != null) return threat; + } + } + + // Targets + if (hasBDAI && aiComponent != null && bdAITargetFieldGetter != null) + { + var target = bdAITargetFieldGetter(aiComponent); // Priority 3: the current vessel's target. + if (target != null) return target; + } + return null; + } + + Type AIModuleType() + { + foreach (var assy in AssemblyLoader.loadedAssemblies) + { + if (assy.assembly.FullName.Contains("BDArmory")) + { + foreach (var t in assy.assembly.GetTypes()) + { + if (t.Name == "BDGenericAIBase") + { + if (CamTools.DEBUG) Debug.Log("[CameraTools]: Found BDGenericAIBase type."); + return t; + } + } + } + } + + return null; + } + + Type WeaponManagerType() + { + foreach (var assy in AssemblyLoader.loadedAssemblies) + { + if (assy.assembly.FullName.Contains("BDArmory")) + { + foreach (var t in assy.assembly.GetTypes()) + { + if (t.Name == "MissileFire") + { + if (CamTools.DEBUG) Debug.Log("[CameraTools]: Found MissileFire type."); + return t; + } + } + } + } + + return null; + } + + public void UpdateAIDogfightTarget() + { + if (hasBDAI && hasBDWM && useBDAutoTarget) + { + newAITarget = GetAITargetedVessel(); + if (newAITarget != null && newAITarget != camTools.dogfightTarget) + { + if (CamTools.DEBUG) + { + var message = "Switching dogfight target to " + newAITarget.vesselName + (camTools.dogfightTarget != null ? " from " + camTools.dogfightTarget.vesselName : ""); + Debug.Log("[CameraTools]: " + message); + CamTools.DebugLog(message); + } + camTools.dogfightTarget = newAITarget; + AItargetUpdateTime = Time.time; + } + } + } + + public void AutoEnableForBDA() + { + if (!hasBDA) return; + try + { + if (bdVesselsSpawningPropertyGetter != null && (bool)bdVesselsSpawningPropertyGetter(null)) + { + if (autoEnableOverride) + return; // Still spawning. + else + { + Debug.Log("[CameraTools]: Deactivating CameraTools while spawning vessels."); + autoEnableOverride = true; + camTools.RevertCamera(); + return; + } + } + + if (bdVesselsSpawningFieldGetter != null && bdVesselsSpawningFieldGetter(bdVesselSpawnerInstance)) // Deprecated. + { + if (autoEnableOverride) + return; // Still spawning. + else + { + Debug.Log("[CameraTools]: Deactivating CameraTools while spawning vessels."); + autoEnableOverride = true; + camTools.RevertCamera(); + return; + } + } + + if (bdTournamentWarpInProgressFieldGetter != null && bdTournamentWarpInProgressFieldGetter(bdBDATournamentInstance)) + { + if (autoEnableOverride) + return; // Still warping. + else + { + Debug.Log("[CameraTools]: Deactivating CameraTools while warping between rounds."); + autoEnableOverride = true; + camTools.RevertCamera(); + return; + } + } + + autoEnableOverride = false; + if (camTools.cameraToolActive) return; // It's already active. + + if (vessel == null || (hasPilotAI && vessel.LandedOrSplashed)) return; // Don't activate for landed/splashed planes. + if (bdCompetitionStartingFieldGetter != null && bdCompetitionStartingFieldGetter(bdCompetitionInstance)) + { + Debug.Log("[CameraTools]: Activating CameraTools for BDArmory competition as competition is starting."); + camTools.cameraActivate(); + return; + } + else if (bdCompetitionIsActiveFieldGetter != null && bdCompetitionIsActiveFieldGetter(bdCompetitionInstance)) + { + Debug.Log("[CameraTools]: Activating CameraTools for BDArmory competition as competition is active."); + UpdateAIDogfightTarget(); + camTools.cameraActivate(); + return; + } + } + catch (Exception e) + { + Debug.LogError("[CameraTools]: Checking competition state of BDArmory failed: " + e.Message); + bdCompetitionIsActiveFieldGetter = null; + bdCompetitionStartingFieldGetter = null; + bdCompetitionInstance = null; + bdVesselsSpawningFieldGetter = null; + bdVesselsSpawningPropertyGetter = null; + bdVesselSpawnerInstance = null; + CheckForBDA(); + } + } + + FieldInfo GetThreatField() + { + Type wmModType = WeaponManagerType(); + if (wmModType == null) return null; + + FieldInfo[] fields = wmModType.GetFields(BindingFlags.Public | BindingFlags.Instance); + foreach (var f in fields) + { + if (f.Name == "incomingThreatVessel") + { + bdWmThreatFieldGetter = ReflectionUtils.CreateGetter(f); + if (CamTools.DEBUG) Debug.Log($"[CameraTools]: Created bdWmThreatFieldGetter."); + return f; + } + } + + return null; + } + + FieldInfo GetMissileField() + { + Type wmModType = WeaponManagerType(); + if (wmModType == null) return null; + + FieldInfo[] fields = wmModType.GetFields(BindingFlags.Public | BindingFlags.Instance); + foreach (var f in fields) + { + if (f.Name == "incomingMissileVessel") + { + bdWmMissileFieldGetter = ReflectionUtils.CreateGetter(f); + if (CamTools.DEBUG) Debug.Log($"[CameraTools]: Created bdWmMissileFieldGetter."); + return f; + } + } + + return null; + } + + FieldInfo GetUnderFireField() + { + Type wmModType = WeaponManagerType(); + if (wmModType == null) return null; + + FieldInfo[] fields = wmModType.GetFields(BindingFlags.Public | BindingFlags.Instance); + foreach (var f in fields) + { + if (f.Name == "underFire") + { + bdWmUnderFireFieldGetter = ReflectionUtils.CreateGetter(f); + if (CamTools.DEBUG) Debug.Log($"[CameraTools]: Created bdWmUnderFireFieldGetter."); + return f; + } + } + + return null; + } + + FieldInfo GetUnderAttackField() + { + Type wmModType = WeaponManagerType(); + if (wmModType == null) return null; + + FieldInfo[] fields = wmModType.GetFields(BindingFlags.Public | BindingFlags.Instance); + foreach (var f in fields) + { + if (f.Name == "underAttack") + { + bdWmUnderAttackFieldGetter = ReflectionUtils.CreateGetter(f); + if (CamTools.DEBUG) Debug.Log("[CameraTools]: Created bdWmUnderAttackFieldGetter."); + return f; + } + } + + return null; + } + + FieldInfo GetAITargetField() + { + Type aiModType = AIModuleType(); + if (aiModType == null) return null; + + FieldInfo[] fields = aiModType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); + foreach (var f in fields) + { + if (f.Name == "targetVessel") + { + bdAITargetFieldGetter = ReflectionUtils.CreateGetter(f); + if (CamTools.DEBUG) Debug.Log("[CameraTools]: Created bdAITargetFieldGetter."); + return f; + } + } + + return null; + } + + public void GetBDVessels() + { + if (!hasBDA || bdLoadedVesselSwitcherVesselsPropertyGetter == null || bdLoadedVesselSwitcherInstance == null) return; + bdActiveVessels = (Dictionary>)bdLoadedVesselSwitcherVesselsPropertyGetter(bdLoadedVesselSwitcherInstance); + _bdWMVessels = bdActiveVessels.SelectMany(kvp => kvp.Value).ToList(); // FIXME Remove this once SI updates the Centroid mode using bdActiveVessels. + _bdWMVesselsLastUpdate = Time.time; + } + + public Vector3 GetCentroid() + { + Vector3 centroid = Vector3.zero; + int count = 1; + + foreach (var v in bdWMVessels) + { + if (v == null || !v.loaded || v.packed) continue; + if ((v.CoM - FlightGlobals.ActiveVessel.CoM).magnitude > 20000) continue; + if (!v.isActiveVessel) + { + centroid += v.transform.position; + ++count; + } + } + centroid /= (float)count; + return centroid; + } + } +} \ No newline at end of file diff --git a/CameraTools/Integration/BetterTimeWarp.cs b/CameraTools/Integration/BetterTimeWarp.cs new file mode 100644 index 00000000..aaac1258 --- /dev/null +++ b/CameraTools/Integration/BetterTimeWarp.cs @@ -0,0 +1,73 @@ +using UnityEngine; +using System; +using System.Reflection; + +namespace CameraTools.Integration +{ + [KSPAddon(KSPAddon.Startup.Flight, false)] + public class BetterTimeWarp : MonoBehaviour + { + public static BetterTimeWarp instance; + public bool hasBetterTimeWarp = false; + + object betterTimeWarpInstance = null; + FieldInfo betterTimeWarpScaleCameraSpeedField = null; + bool betterTimeWarpScaleCameraSpeedOriginalValue = false; + + void Awake() + { + if (instance is not null) Destroy(instance); + instance = this; + } + + void Start() + { + FindBetterTimeWarpScaleWarpSpeed(); // Better Time Warp's ScaleCameraSpeed breaks CameraTools. + } + + void FindBetterTimeWarpScaleWarpSpeed() + { + try + { + foreach (var assy in AssemblyLoader.loadedAssemblies) + { + if (assy.assembly.FullName.Contains("BetterTimeWarp")) // BetterTimeWarpContinued + { + foreach (var type in assy.assembly.GetTypes()) + { + if (type == null) continue; + if (type.Name == "BetterTimeWarp") + { + betterTimeWarpInstance = FindObjectOfType(type); + if (betterTimeWarpInstance != null) + { + foreach (var fieldInfo in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) + { + if (fieldInfo != null && fieldInfo.Name == "ScaleCameraSpeed") + { + betterTimeWarpScaleCameraSpeedField = fieldInfo; + betterTimeWarpScaleCameraSpeedOriginalValue = (bool)fieldInfo.GetValue(betterTimeWarpInstance); + return; + } + } + } + } + } + } + } + } + catch (Exception e) + { + Debug.LogError($"[CameraTools.Integration.BetterTimeWarp]: Failed to locate ScaleCameraSpeed in BetterTimeWarp (Continued): {e.Message}"); + } + } + + public void SetBetterTimeWarpScaleCameraSpeed(bool restore) + { + if (!hasBetterTimeWarp || betterTimeWarpScaleCameraSpeedField == null || !betterTimeWarpScaleCameraSpeedOriginalValue) return; // Not found or it was originally false, so we can ignore it. + if (restore) Debug.Log("[CameraTools.Integration.BetterTimeWarp]: Restoring ScaleCameraSpeed variable in BetterTimeWarp.BetterTimeWarp to true."); + else Debug.Log("[CameraTools.Integration.BetterTimeWarp]: Setting ScaleCameraSpeed variable in BetterTimeWarp.BetterTimeWarp to false as it breaks CameraTools when running in slow-mo."); + betterTimeWarpScaleCameraSpeedField.SetValue(betterTimeWarpInstance, restore); + } + } +} \ No newline at end of file diff --git a/CameraTools/Integration/TimeControl.cs b/CameraTools/Integration/TimeControl.cs new file mode 100644 index 00000000..f64ff82a --- /dev/null +++ b/CameraTools/Integration/TimeControl.cs @@ -0,0 +1,74 @@ +using UnityEngine; +using System; +using System.Reflection; + +namespace CameraTools.Integration +{ + [KSPAddon(KSPAddon.Startup.Flight, false)] + public class TimeControl : MonoBehaviour + { + public static TimeControl instance; + public bool hasTimeControl = false; + + object timeControlInstance = null; + PropertyInfo timeControlCameraZoomFixProperty = null; + bool timeControlCameraZoomFixOriginalValue = false; + + void Awake() + { + if (instance is not null) Destroy(instance); + instance = this; + } + + void Start() + { + FindTimeControlCameraZoomFix(); // Time Control's camera zoom fix breaks CameraTools. + } + + public void FindTimeControlCameraZoomFix() + { + try + { + foreach (var assy in AssemblyLoader.loadedAssemblies) + { + if (assy.assembly.FullName.Contains("TimeControl")) + { + foreach (var type in assy.assembly.GetTypes()) + { + if (type == null) continue; + if (type.Name == "GlobalSettings") + { + timeControlInstance = FindObjectOfType(type); + if (timeControlInstance != null) + { + foreach (var propertyInfo in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (propertyInfo != null && propertyInfo.Name == "CameraZoomFix") + { + timeControlCameraZoomFixProperty = propertyInfo; + timeControlCameraZoomFixOriginalValue = (bool)propertyInfo.GetValue(timeControlInstance); + hasTimeControl = true; + return; + } + } + } + } + } + } + } + } + catch (Exception e) + { + Debug.LogError($"[CameraTools.Integration.TimeControl]: Failed to locate CameraZoomFix in TimeControl: {e.Message}"); + } + } + + public void SetTimeControlCameraZoomFix(bool restore) + { + if (!hasTimeControl || timeControlCameraZoomFixProperty == null || !timeControlCameraZoomFixOriginalValue) return; // Not found or it was originally false, so we can ignore it. + if (restore) Debug.Log("[CameraTools.Integration.TimeControl]: Restoring CameraZoomFix variable in TimeControl.GlobalSettings to true."); + else Debug.Log("[CameraTools.Integration.TimeControl]: Setting CameraZoomFix variable in TimeControl.GlobalSettings to false as it breaks CameraTools when running in slow-mo."); + timeControlCameraZoomFixProperty.SetValue(timeControlInstance, restore && timeControlCameraZoomFixOriginalValue); + } + } +} \ No newline at end of file diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index 31b5dc17..2b0b7e12 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.22.0.0" )] -[assembly: AssemblyFileVersion( "1.22.0" )] +[assembly: AssemblyVersion( "1.23.0.0" )] +[assembly: AssemblyFileVersion( "1.23.0" )] From 66979a7a7a08b1cafdc7cb8ca0ccb11f393530d2 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sat, 3 Sep 2022 16:43:41 +0200 Subject: [PATCH 139/263] Allow deploying to multiple KSP instances when compiling in Linux. --- CameraTools/CameraTools.csproj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CameraTools/CameraTools.csproj b/CameraTools/CameraTools.csproj index c39f4a5f..b8217028 100644 --- a/CameraTools/CameraTools.csproj +++ b/CameraTools/CameraTools.csproj @@ -228,10 +228,12 @@ copy /Y "$(TargetDir)$(Targetname).pdb" "%25KSP_DIR%25\GameData\CameraTools\Plug echo packaging new build... 7za a -tzip -r "$(ProjectDir)Distribution/${ModName}.@(VersionNumber)_`date -u -Iseconds`.zip" "$(ProjectDir)Distribution/*.*" - export KSP_DIR="`cat $(ProjectDir)../../_LocalDev/ksp_dir.txt`" - 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" + + bash -c 'cat $(ProjectDir)../../_LocalDev/ksp_dir.txt | while read KSP_DIR; do + 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! From 452ce1074dfb29cb29709359f0b8baffcdf31a7f Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sat, 3 Sep 2022 16:45:44 +0200 Subject: [PATCH 140/263] Add speed-mode input for keyboard input. --- CameraTools/CamTools.cs | 457 ++++++++++++------ .../GameData/CameraTools/Changelog.txt | 2 + 2 files changed, 312 insertions(+), 147 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 68b4e163..92553b38 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -66,10 +66,14 @@ public class CamTools : MonoBehaviour [CTPersistantField] public string fmZoomInKey = "[9]"; [CTPersistantField] public string fmZoomOutKey = "[3]"; [CTPersistantField] public string fmMovementModifier = "enter"; + [CTPersistantField] public string fmModeToggleKey = "[2]"; bool waitingForTarget = false; bool waitingForPosition = false; bool mouseUp = false; bool editingKeybindings = false; + enum fmModeTypes { Position, Speed }; + fmModeTypes fmMode = fmModeTypes.Position; + Vector4 fmSpeeds = Vector4.zero; // x,y,z,zoom. #endregion #region GUI @@ -636,6 +640,16 @@ void Update() } cameraActivate(); } + + if (Input.GetKeyDown(fmModeToggleKey)) + { + // 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}"); + } } if (Input.GetMouseButtonUp(0)) @@ -739,12 +753,51 @@ void FixedUpdate() { 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, 100f); + if (dogfightDistance <= 1f || dogfightDistance >= 100f) fmSpeeds.z = 0; + if (!autoFOV) + { + zoomExp = Mathf.Clamp(zoomExp + fmSpeeds.w, 1, 8); + if (zoomExp <= 1 || zoomExp >= 8) fmSpeeds.w = 0; + } + else + { + autoZoomMargin = Mathf.Clamp(autoZoomMargin + 10 * fmSpeeds.w, 0, 50); + if (autoZoomMargin <= 0 || autoZoomMargin >= 50) fmSpeeds.w = 0; + } + } break; case ToolModes.StationaryCamera: // Updating of the stationary camera is handled in Update. + if (fmMode == fmModeTypes.Speed) + { + manualPosition += cameraUp * fmSpeeds.y + forwardAxis * fmSpeeds.z + flightCamera.transform.right * fmSpeeds.x; + if (!autoFOV) + { + zoomExp = Mathf.Clamp(zoomExp + fmSpeeds.w, 1f, 8f); + if (zoomExp <= 1f || zoomExp >= 8f) fmSpeeds.w = 0; + } + else + { + autoZoomMargin = Mathf.Clamp(autoZoomMargin + 10 * fmSpeeds.w, 0f, 50f); + if (autoZoomMargin <= 0f || autoZoomMargin >= 50f) fmSpeeds.w = 0; + } + } break; case ToolModes.Pathing: 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, 8f); + if (zoomExp <= 1f || zoomExp >= 8f) fmSpeeds.w = 0; + } break; default: break; @@ -1015,59 +1068,102 @@ void UpdateDogfightCamera() //free move if (enableKeypad && !boundThisFrame) { - 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)) + switch (fmMode) { - 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); - } + case fmModeTypes.Position: + { + 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 (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); - } + //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); + } + } + } + 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; } } @@ -1091,26 +1187,6 @@ void UpdateDogfightCamera() StartDogfightCamera(); } } - - // Vessel newAITarget = null; - // void UpdateAIDogfightTarget() - // { - // if (hasBDAI && hasBDWM && useBDAutoTarget) - // { - // newAITarget = GetAITargetedVessel(); - // if (newAITarget != null && newAITarget != dogfightTarget) - // { - // if (DEBUG) - // { - // message = "Switching dogfight target to " + newAITarget.vesselName + (dogfightTarget != null ? " from " + dogfightTarget.vesselName : ""); - // Debug.Log("[CameraTools]: " + message); - // DebugLog(message); - // } - // dogfightTarget = newAITarget; - // AItargetUpdateTime = Time.time; - // } - // } - // } #endregion #region Stationary Camera @@ -1338,53 +1414,96 @@ void UpdateStationaryCamera() //free move if (enableKeypad && !boundThisFrame) { - if (Input.GetKey(fmUpKey)) - { - manualPosition += cameraUp * freeMoveSpeed * Time.fixedDeltaTime; - } - else if (Input.GetKey(fmDownKey)) - { - manualPosition -= cameraUp * 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 -= flightCamera.transform.right * freeMoveSpeed * Time.fixedDeltaTime; - } - else if (Input.GetKey(fmRightKey)) + switch (fmMode) { - manualPosition += flightCamera.transform.right * freeMoveSpeed * Time.fixedDeltaTime; - } + case fmModeTypes.Position: + { + if (Input.GetKey(fmUpKey)) + { + manualPosition += cameraUp * freeMoveSpeed * Time.fixedDeltaTime; + } + else if (Input.GetKey(fmDownKey)) + { + manualPosition -= cameraUp * 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 -= flightCamera.transform.right * freeMoveSpeed * Time.fixedDeltaTime; + } + else if (Input.GetKey(fmRightKey)) + { + manualPosition += 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); - } + //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); + } + } + } + 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; } } @@ -1509,39 +1628,82 @@ void UpdatePathingCam() if (enableKeypad && !boundThisFrame) { - 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)) + switch (fmMode) { - 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; - } + 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, 8); - } - else if (Input.GetKey(fmZoomOutKey)) - { - zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, 8); + //keyZoom Note: pathing doesn't use autoZoomMargin + 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); + } + } + 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; } } @@ -2801,6 +2963,7 @@ void GuiWindow(int windowID) fmZoomInKey = KeyBinding(fmZoomInKey, "Zoom In", ++line); fmZoomOutKey = KeyBinding(fmZoomOutKey, "Zoom Out", ++line); fmMovementModifier = KeyBinding(fmMovementModifier, "Modifier", ++line); + fmModeToggleKey = KeyBinding(fmModeToggleKey, "FM Mode", ++line); } Rect saveRect = new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth / 2, entryHeight); diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 18bc4ce5..09a94cb0 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -2,6 +2,8 @@ v1.23.0 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-mode input for keyboard input (default toggle [2]). Toggling this resets the speed to zero. v1.22.0 Bugfixes: From b14dab56e20ba1b96938964f5ccd5b5bcab2aa08 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sat, 3 Sep 2022 19:59:50 +0200 Subject: [PATCH 141/263] Add display field-width parameter to numeric input fields. Update numeric input fields when making changes with keyboard input. Disable speed free-move mode when numeric input mode is enabled. --- CameraTools/CamTools.cs | 75 ++++++++++++------- .../GameData/CameraTools/Changelog.txt | 8 +- CameraTools/InputField.cs | 15 +++- 3 files changed, 68 insertions(+), 30 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 92553b38..f398922e 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -405,23 +405,23 @@ void Start() contentWidth = (windowWidth) - (2 * leftIndent); inputFields = new Dictionary { - {"autoZoomMargin", gameObject.AddComponent().Initialise(0, autoZoomMargin, 0f, 50f)}, - {"zoomFactor", gameObject.AddComponent().Initialise(0, zoomFactor, 1f, 1096.63f)}, - {"shakeMultiplier", gameObject.AddComponent().Initialise(0, shakeMultiplier, 0f, 10f)}, - {"dogfightDistance", gameObject.AddComponent().Initialise(0, dogfightDistance, 1f, 100f)}, - {"dogfightOffsetX", gameObject.AddComponent().Initialise(0, dogfightOffsetX, -dogfightMaxOffset, dogfightMaxOffset)}, - {"dogfightOffsetY", gameObject.AddComponent().Initialise(0, dogfightOffsetY, -dogfightMaxOffset, dogfightMaxOffset)}, - {"dogfightLerp", gameObject.AddComponent().Initialise(0, dogfightLerp, 0.01f, 0.5f)}, - {"dogfightRoll", gameObject.AddComponent().Initialise(0, dogfightRoll, 0f, 1f)}, - {"pathingSecondarySmoothing", gameObject.AddComponent().Initialise(0, pathingSecondarySmoothing, 0f, 1f)}, - {"pathingTimeScale", gameObject.AddComponent().Initialise(0, pathingTimeScale, 0.05f, 4f)}, - {"randomModeDogfightChance", gameObject.AddComponent().Initialise(0, randomModeDogfightChance, 0f, 100f)}, - {"randomModeIVAChance", gameObject.AddComponent().Initialise(0, randomModeIVAChance, 0f, 100f)}, - {"randomModeStationaryChance", gameObject.AddComponent().Initialise(0, randomModeStationaryChance, 0f, 100f)}, - {"randomModePathingChance", gameObject.AddComponent().Initialise(0, randomModePathingChance, 0f, 100f)}, - {"freeMoveSpeed", gameObject.AddComponent().Initialise(0, freeMoveSpeed, freeMoveSpeedMin, freeMoveSpeedMax)}, - {"keyZoomSpeed", gameObject.AddComponent().Initialise(0, keyZoomSpeed, keyZoomSpeedMin, keyZoomSpeedMax)}, - {"maxRelV", gameObject.AddComponent().Initialise(0, maxRelV)}, + {"autoZoomMargin", gameObject.AddComponent().Initialise(0, autoZoomMargin, 0f, 50f, 4)}, + {"zoomFactor", gameObject.AddComponent().Initialise(0, zoomFactor, 1f, 1096.63f, 4)}, + {"shakeMultiplier", gameObject.AddComponent().Initialise(0, shakeMultiplier, 0f, 10f, 1)}, + {"dogfightDistance", gameObject.AddComponent().Initialise(0, dogfightDistance, 1f, 100f, 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)}, + {"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, 6)}, }; } @@ -643,12 +643,19 @@ void Update() if (Input.GetKeyDown(fmModeToggleKey)) { - // 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}"); + if (!textInput) + { + // 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 (DEBUG) DebugLog($"Unable to switch to free move mode {fmModeTypes.Speed} while in numeric input mode."); + } } } @@ -1076,31 +1083,37 @@ void UpdateDogfightCamera() { 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, 100f); + if (textInput) inputFields["dogfightDistance"].currentValue = dogfightDistance; } else if (Input.GetKey(fmBackKey)) { dogfightDistance += freeMoveSpeed * Time.fixedDeltaTime; dogfightDistance = Mathf.Clamp(dogfightDistance, 1f, 100f); + 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 @@ -1109,10 +1122,12 @@ void UpdateDogfightCamera() if (Input.GetKey(fmZoomInKey)) { zoomExp = Mathf.Clamp(zoomExp + (keyZoomSpeed * Time.fixedDeltaTime), 1, 8); + if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1); } else if (Input.GetKey(fmZoomOutKey)) { zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, 8); + if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1); } } else @@ -1120,10 +1135,12 @@ void UpdateDogfightCamera() if (Input.GetKey(fmZoomInKey)) { autoZoomMargin = Mathf.Clamp(autoZoomMargin + (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, 50); + if (textInput) inputFields["autoZoomMargin"].currentValue = autoZoomMargin; } else if (Input.GetKey(fmZoomOutKey)) { autoZoomMargin = Mathf.Clamp(autoZoomMargin - (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, 50); + if (textInput) inputFields["autoZoomMargin"].currentValue = autoZoomMargin; } } } @@ -1449,10 +1466,12 @@ void UpdateStationaryCamera() if (Input.GetKey(fmZoomInKey)) { zoomExp = Mathf.Clamp(zoomExp + (keyZoomSpeed * Time.fixedDeltaTime), 1, 8); + if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1); } else if (Input.GetKey(fmZoomOutKey)) { zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, 8); + if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1); } } else @@ -1460,10 +1479,12 @@ void UpdateStationaryCamera() if (Input.GetKey(fmZoomInKey)) { autoZoomMargin = Mathf.Clamp(autoZoomMargin + (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, 50); + if (textInput) inputFields["autoZoomMargin"].currentValue = autoZoomMargin; } else if (Input.GetKey(fmZoomOutKey)) { autoZoomMargin = Mathf.Clamp(autoZoomMargin - (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, 50); + if (textInput) inputFields["autoZoomMargin"].currentValue = autoZoomMargin; } } } @@ -1661,10 +1682,12 @@ void UpdatePathingCam() if (Input.GetKey(fmZoomInKey)) { zoomExp = Mathf.Clamp(zoomExp + (keyZoomSpeed * Time.fixedDeltaTime), 1, 8); + if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1); } else if (Input.GetKey(fmZoomOutKey)) { zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, 8); + if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1); } } break; @@ -1732,6 +1755,7 @@ void UpdatePathingCam() 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; } } @@ -2430,7 +2454,8 @@ void GuiWindow(int windowID) 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. } } line++; @@ -2923,7 +2948,7 @@ void GuiWindow(int windowID) { freeMoveSpeedRaw = Mathf.RoundToInt(GUI.HorizontalSlider(new Rect(leftIndent + contentWidth / 2f - 30, contentTop + (line * entryHeight) + 6f, contentWidth / 2f, entryHeight), freeMoveSpeedRaw, freeMoveSpeedMinRaw, freeMoveSpeedMaxRaw) * 100f) / 100f; freeMoveSpeed = Mathf.Pow(10f, freeMoveSpeedRaw); - GUI.Label(new Rect(leftIndent + contentWidth - 25f, contentTop + (line * entryHeight), 25f, entryHeight), freeMoveSpeed.ToString("G3")); + GUI.Label(new Rect(leftIndent + contentWidth - 25f, contentTop + (line * entryHeight), 25f, entryHeight), freeMoveSpeed.ToString("G4")); } else { diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 09a94cb0..d1b54f6b 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,9 +1,13 @@ v1.23.0 -Bugfixes: +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-mode input for keyboard input (default toggle [2]). Toggling this resets the speed to zero. +- 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: diff --git a/CameraTools/InputField.cs b/CameraTools/InputField.cs index e3c4ced3..7602b8b3 100644 --- a/CameraTools/InputField.cs +++ b/CameraTools/InputField.cs @@ -7,13 +7,22 @@ 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) { this.lastUpdated = lastUpdated; this.currentValue = currentValue; this.minValue = minValue; this.maxValue = maxValue; return this; } + 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("G6"); } } + 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; @@ -48,7 +57,7 @@ void tryParseCurrentValue() currentValue = Math.Min(Math.Max(newValue, minValue), maxValue); lastUpdated = Time.time; } - possibleValue = currentValue.ToString("G6"); + possibleValue = currentValue.ToString($"G{precision}"); } // Parse the current possible value immediately. From c4c1a315f4a4267995bc662b3691505d69dbfdd4 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Mon, 19 Sep 2022 11:12:11 +0200 Subject: [PATCH 142/263] Cache the atmospheric audio sound clips to avoid GC allocations. Initialise the 'maxRelV' numeric field properly so that the field-width parameter is 6, not the min value. --- CameraTools/CTAtmosphericAudioController.cs | 31 ++++++++++++++++--- CameraTools/CamTools.cs | 2 +- .../GameData/CameraTools/Changelog.txt | 4 +++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/CameraTools/CTAtmosphericAudioController.cs b/CameraTools/CTAtmosphericAudioController.cs index a23009ce..4757785c 100644 --- a/CameraTools/CTAtmosphericAudioController.cs +++ b/CameraTools/CTAtmosphericAudioController.cs @@ -1,9 +1,12 @@ using UnityEngine; +using System.Collections.Generic; namespace CameraTools { public class CTAtmosphericAudioController : MonoBehaviour { + static Dictionary audioClips; + AudioSource windAudioSource; AudioSource windHowlAudioSource; AudioSource windTearAudioSource; @@ -19,6 +22,7 @@ public class CTAtmosphericAudioController : MonoBehaviour void Awake() { + if (audioClips is null) audioClips = new Dictionary(); vessel = GetComponent(); windAudioSource = new GameObject("windAS").AddComponent(); @@ -26,7 +30,11 @@ void Awake() windAudioSource.maxDistance = 10000; windAudioSource.dopplerLevel = .35f; windAudioSource.spatialBlend = 1; - AudioClip windclip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/windloop"); + 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); @@ -40,7 +48,12 @@ void Awake() 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; @@ -49,7 +62,12 @@ void Awake() 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; @@ -58,7 +76,12 @@ void Awake() sonicBoomSource.minDistance = 50; sonicBoomSource.maxDistance = 20000; sonicBoomSource.dopplerLevel = 0; - sonicBoomSource.clip = GameDatabase.Instance.GetAudioClip("CameraTools/Sounds/sonicBoom"); + if (!audioClips.TryGetValue("CameraTools/Sounds/sonicBoom", out AudioClip sonicBoomclip)) + { + 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; diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index f398922e..2c7f7ddf 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -421,7 +421,7 @@ void Start() {"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, 6)}, + {"maxRelV", gameObject.AddComponent().Initialise(0, maxRelV, float.MinValue, float.MaxValue, 6)}, }; } diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index d1b54f6b..8cff1fd1 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,7 @@ +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. + v1.23.0 Improvements / Bugfixes: - Fix some memory leaks detected by KSPCF. From 5783c9f41bbab4ede767418a88cb5812d3fd24e0 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 22 Sep 2022 16:37:39 +0200 Subject: [PATCH 143/263] Auto-landing camera. Some fixes for AppleSilicon being buggy in this version of Unity. --- CameraTools/CamTools.cs | 50 ++++++++++++++++++++++++++++++---- CameraTools/CameraTools.csproj | 1 + CameraTools/MathUtils.cs | 38 ++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 CameraTools/MathUtils.cs diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 2c7f7ddf..c83e488d 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -214,6 +214,8 @@ public float autoZoomMargin #endregion #region Stationary Camera Fields + [CTPersistantField] public bool autoLandingPosition = false; + bool autoLandingCamEnabled = false; [CTPersistantField] public bool autoFlybyPosition = false; [CTPersistantField] public bool autoFOV = false; float manualFOV = 60; @@ -520,7 +522,7 @@ void KrakensbaneWarpCorrection() } case ToolModes.StationaryCamera: { - if (maintainInitialVelocity && !randomMode) // Don't maintain velocity when using random mode. + if (maintainInitialVelocity && !randomMode && !autoLandingCamEnabled) // Don't maintain velocity when using random mode or auto landing camera. { if (useOrbital && initialOrbit != null) { @@ -1243,7 +1245,9 @@ void StartStationaryCamera() lastVesselCoM = vessel.CoM; // Camera position. - if (autoFlybyPosition || randomMode) + if (!randomMode && autoLandingPosition && GetAutoLandingPosition()) // Set up a landing shot if possible or fall back on other methods. + { } + else if (autoFlybyPosition || randomMode) { setPresetOffset = false; @@ -1345,6 +1349,39 @@ void StartStationaryCamera() if (hasSavedRotation) { flightCamera.transform.rotation = savedRotation; } } + /// + /// 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.transform.position + 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.transform.position).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.transform.position + 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; @@ -1356,7 +1393,7 @@ void UpdateStationaryCamera() debug2Messages.Clear(); if (useAudioEffects) { - speedOfSound = 233 * Math.Sqrt(1 + (FlightGlobals.getExternalTemperature(vessel.GetWorldPos3D(), vessel.mainBody) / 273.15)); + speedOfSound = 233 * MathUtils.Sqrt(1 + (FlightGlobals.getExternalTemperature(vessel.GetWorldPos3D(), vessel.mainBody) / 273.15)); //Debug.Log("[CameraTools]: speed of sound: " + speedOfSound); } @@ -2543,12 +2580,13 @@ void GuiWindow(int windowID) } } autoFlybyPosition = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), autoFlybyPosition, "Auto Flyby Position"); - if (autoFlybyPosition) manualOffset = false; + autoLandingPosition = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), autoLandingPosition, "Auto Landing Position"); ; + if (autoFlybyPosition || autoLandingPosition) { manualOffset = false; } manualOffset = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), manualOffset, "Manual Flyby Position"); Color origGuiColor = GUI.color; if (manualOffset) - { autoFlybyPosition = false; } - else + { 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), "Fwd:", leftLabel); diff --git a/CameraTools/CameraTools.csproj b/CameraTools/CameraTools.csproj index b8217028..96b5d841 100644 --- a/CameraTools/CameraTools.csproj +++ b/CameraTools/CameraTools.csproj @@ -62,6 +62,7 @@ + diff --git a/CameraTools/MathUtils.cs b/CameraTools/MathUtils.cs new file mode 100644 index 00000000..e949be77 --- /dev/null +++ b/CameraTools/MathUtils.cs @@ -0,0 +1,38 @@ +using UnityEngine; +using System.Globalization; + +namespace CameraTools +{ + public static class MathUtils + { + // This is a fun workaround for M1-chip Macs (Apple Silicon). Specific issue the workaround is for is here: + // https://issuetracker.unity3d.com/issues/m1-incorrect-calculation-of-values-using-multiplication-with-mathf-dot-sqrt-when-an-unused-variable-is-declared + // Borrowed from BDArmoryPlus. + public static float Sqrt(float value) => (OSUtils.AppleSilicon) ? SqrtARM(value) : (float)System.Math.Sqrt((double)value); + public static double Sqrt(double value) => (OSUtils.AppleSilicon) ? SqrtARM(value) : System.Math.Sqrt(value); + + private static float SqrtARM(float value) + { + float sqrt = (float)System.Math.Sqrt((double)value); + float sqrt1 = 1f * sqrt; + return sqrt1; + } + private static double SqrtARM(double value) + { + double sqrt = System.Math.Sqrt(value); + double sqrt1 = 1d * sqrt; + return sqrt1; + } + } + + [KSPAddon(KSPAddon.Startup.MainMenu, true)] + internal class OSUtils : MonoBehaviour + { + static public bool AppleSilicon = false; + void Awake() + { + // Check for Apple Processor + AppleSilicon = CultureInfo.InvariantCulture.CompareInfo.IndexOf(SystemInfo.processorType, "Apple", CompareOptions.IgnoreCase) >= 0; + } + } +} \ No newline at end of file From e667000961a8cdf9053ac315708a3b98be7f61f1 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 22 Sep 2022 17:04:00 +0200 Subject: [PATCH 144/263] Update changelog and bump version number. --- .../Distribution/GameData/CameraTools/CameraTools.version | 2 +- .../Distribution/GameData/CameraTools/Changelog.txt | 8 ++++++++ CameraTools/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index 0c8bf394..ea91f855 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -5,7 +5,7 @@ "CHANGE_LOG_URL": "https://github.com/BrettRyland/CameraTools/blob/master/CameraTools/Distribution/GameData/CameraTools/Changelog.txt", "VERSION": { "MAJOR": 1, - "MINOR": 23, + "MINOR": 24, "PATCH": 0, "BUILD": 0 }, diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 8cff1fd1..6bbfa487 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,6 +1,14 @@ +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: diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index 2b0b7e12..02383d2c 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.23.0.0" )] -[assembly: AssemblyFileVersion( "1.23.0" )] +[assembly: AssemblyVersion( "1.24.0.0" )] +[assembly: AssemblyFileVersion( "1.24.0" )] From 1c65e731ba55eac60217b172e898a3fc10bca892 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 8 Nov 2022 19:40:32 +0100 Subject: [PATCH 145/263] AudioSource debugging. Save and restore the other fields that got adjusted too. --- CameraTools/CTPartAudioController.cs | 6 +++--- CameraTools/CamTools.cs | 15 +++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CameraTools/CTPartAudioController.cs b/CameraTools/CTPartAudioController.cs index 70908605..57cb2908 100644 --- a/CameraTools/CTPartAudioController.cs +++ b/CameraTools/CTPartAudioController.cs @@ -8,7 +8,6 @@ public class CTPartAudioController : MonoBehaviour public AudioSource audioSource; - float origMinDist = 1; float origMaxDist = 1; @@ -17,12 +16,12 @@ public class CTPartAudioController : MonoBehaviour AudioRolloffMode origRolloffMode; + // Note: other fields are adjusted in CamTools.SetDoppler and CamTools.ResetDoppler + void Awake() { part = GetComponentInParent(); vessel = part.vessel; - - CamTools.OnResetCTools += OnResetCTools; } void Start() @@ -38,6 +37,7 @@ void Start() origRolloffMode = audioSource.rolloffMode; audioSource.rolloffMode = AudioRolloffMode.Logarithmic; audioSource.spatialBlend = 1; + CamTools.OnResetCTools += OnResetCTools; } void FixedUpdate() diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index c83e488d..23dd509d 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -154,7 +154,7 @@ enum fmModeTypes { Position, Speed }; #region Audio Fields AudioSource[] audioSources; - float[] originalAudioSourceDoppler; + (float dopplerLevel, AudioVelocityUpdateMode velocityUpdateMode, bool bypassEffects, float spatialBlend, bool spatialize)[] originalAudioSourceDoppler; HashSet excludeAudioSources = new HashSet { "MusicLogic", "windAS", "windHowlAS", "windTearAS", "sonicBoomAS" }; // Don't adjust music or atmospheric audio. bool hasSetDoppler = false; [CTPersistantField] public bool useAudioEffects = true; @@ -2113,14 +2113,14 @@ void SetDoppler(bool includeActiveVessel) // 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]; + originalAudioSourceDoppler = new (float, AudioVelocityUpdateMode, bool, float, bool)[audioSources.Length]; // Debug.Log($"DEBUG AudioSource pitch: "+ string.Join(", ", audioSources.Where(a => a.isPlaying).Select(a => $"{a.name}: {a.pitch}"))); for (int i = 0; i < audioSources.Length; i++) { // Debug.Log("CameraTools.DEBUG audioSources: " + string.Join(", ", audioSources.Select(a => a.name))); if (excludeAudioSources.Contains(audioSources[i].name)) continue; - originalAudioSourceDoppler[i] = audioSources[i].dopplerLevel; + originalAudioSourceDoppler[i] = (audioSources[i].dopplerLevel, audioSources[i].velocityUpdateMode, audioSources[i].bypassEffects, audioSources[i].spatialBlend, audioSources[i].spatialize); if (!includeActiveVessel) { @@ -2140,7 +2140,7 @@ void SetDoppler(bool includeActiveVessel) CTPartAudioController pa = audioSources[i].gameObject.GetComponent(); if (pa == null) pa = audioSources[i].gameObject.AddComponent(); pa.audioSource = audioSources[i]; - // if (audioSources[i].isPlaying) Debug.Log($"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}"); + if (DEBUG && audioSources[i].isPlaying) Debug.Log($"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}"); } } @@ -2157,8 +2157,11 @@ void ResetDoppler() for (int i = 0; i < audioSources.Length; i++) { if (audioSources[i] == null || excludeAudioSources.Contains(audioSources[i].name)) continue; - audioSources[i].dopplerLevel = originalAudioSourceDoppler[i]; - audioSources[i].velocityUpdateMode = AudioVelocityUpdateMode.Auto; + audioSources[i].dopplerLevel = originalAudioSourceDoppler[i].dopplerLevel; + audioSources[i].velocityUpdateMode = originalAudioSourceDoppler[i].velocityUpdateMode; + audioSources[i].bypassEffects = originalAudioSourceDoppler[i].bypassEffects; + audioSources[i].spatialBlend = originalAudioSourceDoppler[i].spatialBlend; + audioSources[i].spatialize = originalAudioSourceDoppler[i].spatialize; } hasSetDoppler = false; From 357fb60ca92348e37bb0a69b283b92be1a9aac3f Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 8 Nov 2022 19:40:55 +0100 Subject: [PATCH 146/263] Inertial chase mode for dogfight camera. --- CameraTools/CamTools.cs | 65 ++++++++++++++++++++++++++++++---------- CameraTools/MathUtils.cs | 6 ++++ 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 23dd509d..4c3c083a 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -174,8 +174,12 @@ enum fmModeTypes { Position, Speed }; [CTPersistantField] public float dogfightOffsetX = 10f; [CTPersistantField] public float dogfightOffsetY = 4f; float dogfightMaxOffset = 50; + [CTPersistantField] public bool dogfightInertialChaseMode = false; [CTPersistantField] public float dogfightLerp = 0.2f; [CTPersistantField] public float dogfightRoll = 0f; + [CTPersistantField] public float dogfightInertialFactor = 0f; + Vector3 dogfightLerpDelta = default; + Vector3 dogfightLerpMomentum = default; Quaternion dogfightCameraRoll = Quaternion.identity; Vector3 dogfightCameraRollUp = Vector3.up; [CTPersistantField] public float autoZoomMarginDogfight = 20; @@ -415,6 +419,7 @@ void Start() {"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, 0.1f, 3)}, {"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)}, @@ -895,7 +900,11 @@ void StartDogfightCamera() } if (DEBUG) { Debug.Log("[CameraTools]: Starting dogfight camera."); DebugLog("Starting dogfight camera"); } - if (!dogfightTarget) + if (dogfightTarget) + { + dogfightVelocityChase = false; + } + else { if (false && randomMode && rng.Next(3) == 0) { @@ -906,10 +915,6 @@ void StartDogfightCamera() dogfightVelocityChase = true; } } - else - { - dogfightVelocityChase = false; - } dogfightPrevTarget = dogfightTarget; @@ -1015,22 +1020,37 @@ void UpdateDogfightCamera() { dogfightCameraRollUp = cameraUp; } - - Vector3 offsetDirection = Vector3.Cross(dogfightCameraRollUp, dogfightLastTargetPosition - vessel.CoM).normalized; // FIXME This is changing when in high warp mode. Also, check this when suborbital (not changing). - Vector3 camPos = vessel.CoM + ((vessel.CoM - dogfightLastTargetPosition).normalized * dogfightDistance) + (dogfightOffsetX * offsetDirection) + (dogfightOffsetY * dogfightCameraRollUp); + Vector3 lagDirection = (dogfightLastTargetPosition - vessel.CoM).normalized; + Vector3 offsetDirectionY = dogfightInertialChaseMode ? Quaternion.RotateTowards(Quaternion.identity, Quaternion.FromToRotation(cameraUp, -vessel.ReferenceTransform.forward), Vector3.Angle(cameraUp, -vessel.ReferenceTransform.forward)) * cameraUp : dogfightCameraRollUp; + Vector3 offsetDirectionX = Vector3.Cross(offsetDirectionY, lagDirection).normalized; + Vector3 camPos = vessel.CoM + (-lagDirection * dogfightDistance) + (dogfightOffsetX * offsetDirectionX) + (dogfightOffsetY * offsetDirectionY); Vector3 localCamPos = cameraParent.transform.InverseTransformPoint(camPos); + if (dogfightInertialChaseMode) + { + dogfightLerpMomentum /= dogfightLerpMomentum.sqrMagnitude * 2f / dogfightDistance + 1f; + dogfightLerpMomentum += dogfightLerpDelta * dogfightInertialFactor; + dogfightLerpDelta = -flightCamera.transform.localPosition; + } flightCamera.transform.localPosition = Vector3.Lerp(flightCamera.transform.localPosition, localCamPos, dogfightLerp); + if (dogfightInertialChaseMode) + { + flightCamera.transform.localPosition += dogfightLerpMomentum; + dogfightLerpDelta += flightCamera.transform.localPosition; + if (dogfightLerpDelta.sqrMagnitude > dogfightDistance * dogfightDistance) dogfightLerpDelta *= dogfightDistance / dogfightLerpDelta.magnitude; + } if (DEBUG2 && Time.deltaTime > 0) { debug2Messages.Clear(); Debug2Log("time scale: " + Time.timeScale.ToString("G3") + ", Δt: " + Time.fixedDeltaTime.ToString("G3")); - Debug2Log("offsetDirection: " + offsetDirection.ToString("G3")); + Debug2Log("offsetDirection: " + offsetDirectionX.ToString("G3")); Debug2Log("target offset: " + ((vessel.CoM - dogfightLastTargetPosition).normalized * dogfightDistance).ToString("G3")); - Debug2Log("xOff: " + (dogfightOffsetX * offsetDirection).ToString("G3")); + 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") + ", " + flightCamera.transform.localPosition.ToString("G3")); + Debug2Log($"lerp momentum: {dogfightLerpMomentum:G3}"); + Debug2Log($"lerp delta: {dogfightLerpDelta:G3}"); } //rotation @@ -2501,7 +2521,7 @@ void GuiWindow(int windowID) line++; useAudioEffects = GUI.Toggle(LabelRect(++line), useAudioEffects, "Use Audio Effects"); - if (bdArmory.hasBDA) bdArmory.autoEnableForBDA = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), bdArmory.autoEnableForBDA, "Auto-Enable for BDArmory"); + if (bdArmory.hasBDA) bdArmory.autoEnableForBDA = GUI.Toggle(LabelRect(++line), bdArmory.autoEnableForBDA, "Auto-Enable for BDArmory"); line++; if (autoFOV && toolMode != ToolModes.Pathing) @@ -2695,14 +2715,14 @@ void GuiWindow(int windowID) bdArmory.useCentroid = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight - 2), bdArmory.useCentroid, "Target Dogfight Centroid"); } - line++; + ++line; - GUI.Label(LeftRect(++line), "Distance: " + dogfightDistance.ToString("G3")); + GUI.Label(LeftRect(++line), $"Distance: {dogfightDistance:G3}"); if (!textInput) { line += 0.15f; - dogfightDistance = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), dogfightDistance, 1f, 100f); - if (!enableKeypad) dogfightDistance = Mathf.RoundToInt(dogfightDistance * 2f) / 2f; + dogfightDistance = GUI.HorizontalSlider(LabelRect(++line), dogfightDistance, 1f, 100f); + if (!enableKeypad) dogfightDistance = MathUtils.RoundToUnit(dogfightDistance, 0.5f); } else { @@ -2746,6 +2766,21 @@ void GuiWindow(int windowID) inputFields["dogfightRoll"].tryParseValue(GUI.TextField(QuarterRect(line, 3), inputFields["dogfightRoll"].possibleValue, 8, inputFieldStyle)); dogfightRoll = inputFields["dogfightRoll"].currentValue; } + + GUI.Label(LeftRect(++line), $"Camera Inertia: {dogfightInertialFactor:G3}"); + if (!textInput) + { + line += 0.15f; + dogfightInertialFactor = MathUtils.RoundToUnit(GUI.HorizontalSlider(RightRect(line), dogfightInertialFactor, 0f, 0.1f), 0.01f); + } + else + { + inputFields["dogfightInertialFactor"].tryParseValue(GUI.TextField(RightRect(line), inputFields["dogfightInertialFactor"].possibleValue, 8, inputFieldStyle)); + dogfightInertialFactor = inputFields["dogfightInertialFactor"].currentValue; + } + + if (dogfightInertialChaseMode != (dogfightInertialChaseMode = GUI.Toggle(LabelRect(++line), dogfightInertialChaseMode, "Inertial Chase Mode"))) + { StartDogfightCamera(); } } else if (toolMode == ToolModes.Pathing) { diff --git a/CameraTools/MathUtils.cs b/CameraTools/MathUtils.cs index e949be77..a1958e76 100644 --- a/CameraTools/MathUtils.cs +++ b/CameraTools/MathUtils.cs @@ -23,6 +23,12 @@ private static double SqrtARM(double value) double sqrt1 = 1d * sqrt; return sqrt1; } + + public static float RoundToUnit(float value, float unit = 1f) + { + var rounded = Mathf.Round(value / unit) * unit; + return (unit % 1 != 0) ? rounded : Mathf.Round(rounded); // Fix near-integer loss of precision. + } } [KSPAddon(KSPAddon.Startup.MainMenu, true)] From 644b44c139a3c31e26a22f7952b536e9dd4eb30b Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Wed, 9 Nov 2022 23:26:54 +0100 Subject: [PATCH 147/263] 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 the latest BDA+). 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). Standardise many of the GUI Rects. --- CameraTools/CamTools.cs | 136 ++++++++++-------- .../GameData/CameraTools/CameraTools.version | 2 +- .../GameData/CameraTools/Changelog.txt | 9 ++ CameraTools/Integration/BDArmory.cs | 114 +++++++++++++-- CameraTools/Properties/AssemblyInfo.cs | 4 +- 5 files changed, 192 insertions(+), 73 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 4c3c083a..38983ab9 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -2423,12 +2423,26 @@ void OnGUI() 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 / 2, 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 / 4, contentTop + line * entryHeight, contentWidth / 4, 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) + { return new Rect(leftIndent + contentWidth - 25f, contentTop + line * entryHeight, 25f, entryHeight); } + Rect SliderRect(float line, float indent) + { return new Rect(leftIndent + indent, contentTop + line * entryHeight + 6f, contentWidth - indent - 30f, 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); @@ -2517,6 +2531,7 @@ void GuiWindow(int windowID) 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. } + bdArmory.ToggleInputFields(textInput); } line++; @@ -2590,7 +2605,7 @@ void GuiWindow(int windowID) 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)) + if (FlightGlobals.ActiveVessel != null && GUI.Button(ThinRect(++line), posButtonText)) { if (setPresetOffset) { @@ -2602,10 +2617,10 @@ void GuiWindow(int windowID) mouseUp = false; } } - autoFlybyPosition = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), autoFlybyPosition, "Auto Flyby Position"); - autoLandingPosition = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), autoLandingPosition, "Auto Landing Position"); ; + autoFlybyPosition = GUI.Toggle(LabelRect(++line), autoFlybyPosition, "Auto Flyby Position"); + autoLandingPosition = GUI.Toggle(LabelRect(++line), autoLandingPosition, "Auto Landing Position"); ; if (autoFlybyPosition || autoLandingPosition) { manualOffset = false; } - manualOffset = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), manualOffset, "Manual Flyby Position"); + manualOffset = GUI.Toggle(LabelRect(++line), manualOffset, "Manual Flyby Position"); Color origGuiColor = GUI.color; if (manualOffset) { autoFlybyPosition = false; autoLandingPosition = false; } @@ -2645,31 +2660,31 @@ void GuiWindow(int windowID) string targetText = "None"; if (camTarget != null) targetText = camTarget.gameObject.name; - GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), "Camera Target: " + targetText, leftLabel); + GUI.Label(LabelRect(++line), "Camera Target: " + targetText, leftLabel); string tgtButtonText = "Set Target w/ Click"; if (waitingForTarget) tgtButtonText = "waiting..."; - if (GUI.Button(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight - 2), tgtButtonText)) + if (GUI.Button(ThinRect(++line), tgtButtonText)) { waitingForTarget = true; mouseUp = false; } - if (GUI.Button(new Rect(leftIndent, contentTop + (++line * entryHeight), (contentWidth / 2) - 2, entryHeight - 2), "Target Self")) + if (GUI.Button(ThinHalfRect(++line, 0), "Target Self")) { 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), "Clear Target")) { camTarget = null; hasTarget = false; } - targetCoM = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight - 2), targetCoM, "Vessel Center of Mass"); - if (camTarget == null) saveRotation = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight - 2), saveRotation, "Save Rotation"); + targetCoM = GUI.Toggle(ThinRect(++line), targetCoM, "Vessel Center of Mass"); + if (camTarget == null) saveRotation = GUI.Toggle(ThinRect(++line), saveRotation, "Save Rotation"); if (!saveRotation) hasSavedRotation = false; } else if (toolMode == ToolModes.DogfightCamera) { - GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), "Secondary Target:"); + GUI.Label(ThinRect(++line), "Secondary Target:"); string tVesselLabel; if (showingVesselList) { tVesselLabel = "Clear"; } @@ -2677,7 +2692,7 @@ void GuiWindow(int windowID) { tVesselLabel = dogfightTarget.vesselName; } else { tVesselLabel = "None"; } - if (GUI.Button(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), tVesselLabel)) + if (GUI.Button(LabelRect(++line), tVesselLabel)) { if (showingVesselList) { @@ -2704,25 +2719,35 @@ void GuiWindow(int windowID) } if (bdArmory.hasBDA) { - if (!bdArmory.useCentroid) + if (bdArmory.hasBDAI) { - if (bdArmory.hasBDAI) + if (bdArmory.useBDAutoTarget != (bdArmory.useBDAutoTarget = GUI.Toggle(ThinRect(++line), bdArmory.useBDAutoTarget, "BDA AI Auto Target")) && bdArmory.useBDAutoTarget) + { bdArmory.useCentroid = false; } + GUI.Label(SliderLabelLeft(++line, 110f), "Minimum Interval:"); + if (!textInput) { - bdArmory.useBDAutoTarget = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight - 2), bdArmory.useBDAutoTarget, "BDA AI Auto Target"); - bdArmory.autoTargetIncomingMissiles = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight - 2), bdArmory.autoTargetIncomingMissiles, "Target Incoming Missiles"); + bdArmory.AItargetMinimumUpdateInterval = MathUtils.RoundToUnit(GUI.HorizontalSlider(SliderRect(line, 110f), bdArmory.AItargetMinimumUpdateInterval, 0.5f, 5f), 0.5f); + GUI.Label(SliderLabelRight(line), $"{bdArmory.AItargetMinimumUpdateInterval:F1}s"); } + else + { + bdArmory.inputFields["AItargetUpdateInterval"].tryParseValue(GUI.TextField(RightRect(line), bdArmory.inputFields["AItargetUpdateInterval"].possibleValue, 8, inputFieldStyle)); + bdArmory.AItargetMinimumUpdateInterval = bdArmory.inputFields["AItargetUpdateInterval"].currentValue; + } + bdArmory.autoTargetIncomingMissiles = GUI.Toggle(ThinRect(++line), bdArmory.autoTargetIncomingMissiles, "Target Incoming Missiles"); } - bdArmory.useCentroid = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight - 2), bdArmory.useCentroid, "Target Dogfight Centroid"); + if (bdArmory.useCentroid != (bdArmory.useCentroid = GUI.Toggle(ThinRect(++line), bdArmory.useCentroid, "Target Dogfight Centroid")) && bdArmory.useCentroid) + { bdArmory.useBDAutoTarget = false; } } ++line; - GUI.Label(LeftRect(++line), $"Distance: {dogfightDistance:G3}"); + GUI.Label(SliderLabelLeft(++line, 55f), $"Distance:"); if (!textInput) { - line += 0.15f; - dogfightDistance = GUI.HorizontalSlider(LabelRect(++line), dogfightDistance, 1f, 100f); - if (!enableKeypad) dogfightDistance = MathUtils.RoundToUnit(dogfightDistance, 0.5f); + dogfightDistance = GUI.HorizontalSlider(SliderRect(++line, 0f), dogfightDistance, 1f, 100f); + if (!enableKeypad) dogfightDistance = MathUtils.RoundToUnit(dogfightDistance, 1f); + GUI.Label(SliderLabelRight(line), $"{dogfightDistance:G3}m"); } else { @@ -2733,22 +2758,22 @@ void GuiWindow(int windowID) GUI.Label(LeftRect(++line), "Offset:"); if (!textInput) { - GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), 15f, entryHeight), "X: "); - dogfightOffsetX = GUI.HorizontalSlider(new Rect(leftIndent + 15f, contentTop + (line * entryHeight) + 6f, contentWidth - 45f, entryHeight), dogfightOffsetX, -dogfightMaxOffset, dogfightMaxOffset); - if (!enableKeypad) dogfightOffsetX = Mathf.RoundToInt(dogfightOffsetX * 2f) / 2f; - GUI.Label(new Rect(leftIndent + contentWidth - 25f, contentTop + (line * entryHeight), 25f, entryHeight), dogfightOffsetX.ToString("G3")); - GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), 15, entryHeight), "Y: "); - dogfightOffsetY = GUI.HorizontalSlider(new Rect(leftIndent + 15f, contentTop + (line * entryHeight) + 6f, contentWidth - 45f, entryHeight), dogfightOffsetY, -dogfightMaxOffset, dogfightMaxOffset); - if (!enableKeypad) dogfightOffsetY = Mathf.RoundToInt(dogfightOffsetY * 2f) / 2f; - GUI.Label(new Rect(leftIndent + contentWidth - 25f, contentTop + (line * entryHeight), 25, entryHeight), dogfightOffsetY.ToString("G3")); + GUI.Label(SliderLabelLeft(++line, 15f), "X: "); + dogfightOffsetX = GUI.HorizontalSlider(SliderRect(line, 15f), dogfightOffsetX, -dogfightMaxOffset, dogfightMaxOffset); + if (!enableKeypad) dogfightOffsetX = MathUtils.RoundToUnit(dogfightOffsetX, 1f); + GUI.Label(SliderLabelRight(line), $"{dogfightOffsetX:G3}m"); + GUI.Label(SliderLabelLeft(++line, 15f), "Y: "); + dogfightOffsetY = GUI.HorizontalSlider(SliderRect(line, 15f), dogfightOffsetY, -dogfightMaxOffset, dogfightMaxOffset); + if (!enableKeypad) dogfightOffsetY = MathUtils.RoundToUnit(dogfightOffsetY, 1f); + GUI.Label(SliderLabelRight(line), $"{dogfightOffsetY:G3}m"); line += 0.5f; - GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), 30f, entryHeight), "Lerp: "); - dogfightLerp = Mathf.RoundToInt(GUI.HorizontalSlider(new Rect(leftIndent + 30f, contentTop + (line * entryHeight) + 6f, contentWidth - 60f, entryHeight), dogfightLerp * 100f, 1f, 50f)) / 100f; - GUI.Label(new Rect(leftIndent + contentWidth - 25f, contentTop + (line * entryHeight), 25f, entryHeight), dogfightLerp.ToString("G3")); - GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), 30f, entryHeight), "Roll: "); - dogfightRoll = Mathf.RoundToInt(GUI.HorizontalSlider(new Rect(leftIndent + 30f, contentTop + (line * entryHeight) + 6f, contentWidth - 60f, entryHeight), dogfightRoll * 20f, 0f, 20f)) / 20f; - GUI.Label(new Rect(leftIndent + contentWidth - 25f, contentTop + (line * entryHeight), 25f, entryHeight), dogfightRoll.ToString("G3")); + GUI.Label(SliderLabelLeft(++line, 30f), "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), "Roll: "); + dogfightRoll = Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, 30f), dogfightRoll * 20f, 0f, 20f)) / 20f; + GUI.Label(SliderLabelRight(line), $"{dogfightRoll:G3}"); line += 0.15f; } else @@ -2767,11 +2792,11 @@ void GuiWindow(int windowID) dogfightRoll = inputFields["dogfightRoll"].currentValue; } - GUI.Label(LeftRect(++line), $"Camera Inertia: {dogfightInertialFactor:G3}"); + GUI.Label(SliderLabelLeft(++line, 95f), $"Camera Inertia:"); if (!textInput) { - line += 0.15f; - dogfightInertialFactor = MathUtils.RoundToUnit(GUI.HorizontalSlider(RightRect(line), dogfightInertialFactor, 0f, 0.1f), 0.01f); + dogfightInertialFactor = MathUtils.RoundToUnit(GUI.HorizontalSlider(SliderRect(line, 95f), dogfightInertialFactor, 0f, 0.1f), 0.01f); + GUI.Label(SliderLabelRight(line), $"{dogfightInertialFactor:G3}"); } else { @@ -2786,17 +2811,17 @@ void GuiWindow(int windowID) { if (selectedPathIndex >= 0) { - GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), "Path:"); + GUI.Label(LabelRect(++line), "Path:"); currentPath.pathName = GUI.TextField(new Rect(leftIndent + 34, contentTop + (line * entryHeight), contentWidth - 34, entryHeight), currentPath.pathName); } else - { GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), "Path: None"); } + { GUI.Label(LabelRect(++line), "Path: None"); } line += 0.25f; - if (GUI.Button(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), "Open Path")) + if (GUI.Button(LabelRect(++line), "Open Path")) { TogglePathList(); } - if (GUI.Button(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth / 2f, entryHeight), "New Path")) + if (GUI.Button(HalfRect(++line, 0), "New Path")) { CreateNewPath(); } - if (GUI.Button(new Rect(leftIndent + (contentWidth / 2f), contentTop + (line * entryHeight), contentWidth / 2f, entryHeight), "Delete Path")) + if (GUI.Button(HalfRect(line, 1), "Delete Path")) { DeletePath(selectedPathIndex); } line += 0.25f; @@ -2827,7 +2852,7 @@ void GuiWindow(int windowID) inputFields["pathingTimeScale"].tryParseValue(GUI.TextField(RightRect(line), inputFields["pathingTimeScale"].possibleValue, 8, inputFieldStyle)); currentPath.timeScale = inputFields["pathingTimeScale"].currentValue; } - if (GUI.Button(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), useRealTime ? "Real-time" : "In-Game time")) + if (GUI.Button(LabelRect(++line), useRealTime ? "Real-time" : "In-Game time")) { useRealTime = !useRealTime; } @@ -3016,15 +3041,15 @@ void GuiWindow(int windowID) } line += 0.25f; - enableKeypad = GUI.Toggle(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), enableKeypad, "Keypad Control"); + enableKeypad = GUI.Toggle(LabelRect(++line), enableKeypad, "Keypad Control"); if (enableKeypad) { - GUI.Label(LeftRect(++line), "Move Speed:"); + GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), "Move Speed:"); if (!textInput) { - freeMoveSpeedRaw = Mathf.RoundToInt(GUI.HorizontalSlider(new Rect(leftIndent + contentWidth / 2f - 30, contentTop + (line * entryHeight) + 6f, contentWidth / 2f, entryHeight), freeMoveSpeedRaw, freeMoveSpeedMinRaw, freeMoveSpeedMaxRaw) * 100f) / 100f; + freeMoveSpeedRaw = Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, contentWidth / 2f - 30f), freeMoveSpeedRaw, freeMoveSpeedMinRaw, freeMoveSpeedMaxRaw) * 100f) / 100f; freeMoveSpeed = Mathf.Pow(10f, freeMoveSpeedRaw); - GUI.Label(new Rect(leftIndent + contentWidth - 25f, contentTop + (line * entryHeight), 25f, entryHeight), freeMoveSpeed.ToString("G4")); + GUI.Label(SliderLabelRight(line), freeMoveSpeed.ToString("G4")); } else { @@ -3032,12 +3057,12 @@ void GuiWindow(int windowID) freeMoveSpeed = inputFields["freeMoveSpeed"].currentValue; } - GUI.Label(LeftRect(++line), "Zoom Speed:"); + GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), "Zoom Speed:"); if (!textInput) { - zoomSpeedRaw = Mathf.RoundToInt(GUI.HorizontalSlider(new Rect(leftIndent + contentWidth / 2f - 30f, contentTop + (line * entryHeight) + 6f, contentWidth / 2f, entryHeight), zoomSpeedRaw, zoomSpeedMinRaw, zoomSpeedMaxRaw) * 100f) / 100f; + zoomSpeedRaw = Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, contentWidth / 2f - 30f), zoomSpeedRaw, zoomSpeedMinRaw, zoomSpeedMaxRaw) * 100f) / 100f; keyZoomSpeed = Mathf.Pow(10f, zoomSpeedRaw); - GUI.Label(new Rect(leftIndent + contentWidth - 25f, contentTop + (line * entryHeight), 25f, entryHeight), keyZoomSpeed.ToString("G3")); + GUI.Label(SliderLabelRight(line), keyZoomSpeed.ToString("G3")); } else { @@ -3048,7 +3073,7 @@ void GuiWindow(int windowID) line++; // Key bindings - if (GUI.Button(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), "Edit Keybindings")) + if (GUI.Button(LabelRect(++line), "Edit Keybindings")) { editingKeybindings = !editingKeybindings; } if (editingKeybindings) { @@ -3067,12 +3092,11 @@ void GuiWindow(int windowID) fmModeToggleKey = KeyBinding(fmModeToggleKey, "FM Mode", ++line); } - Rect saveRect = new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth / 2, entryHeight); + Rect saveRect = HalfRect(++line, 0); if (GUI.Button(saveRect, "Save")) { Save(); } - Rect loadRect = new Rect(saveRect); - loadRect.x += contentWidth / 2; + Rect loadRect = HalfRect(line, 1); if (GUI.Button(loadRect, "Reload")) { if (isPlayingPath) StopPlayingPath(); @@ -3083,7 +3107,7 @@ void GuiWindow(int windowID) if (timeSinceLastSaved < 1) { ++line; - GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth, entryHeight), timeSinceLastSaved < 0.5 ? "Saving..." : "Saved.", centerLabel); + GUI.Label(LabelRect(++line), timeSinceLastSaved < 0.5 ? "Saving..." : "Saved.", centerLabel); } //fix length diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index ea91f855..9d0bddab 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -5,7 +5,7 @@ "CHANGE_LOG_URL": "https://github.com/BrettRyland/CameraTools/blob/master/CameraTools/Distribution/GameData/CameraTools/Changelog.txt", "VERSION": { "MAJOR": 1, - "MINOR": 24, + "MINOR": 25, "PATCH": 0, "BUILD": 0 }, diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 6bbfa487..94a37fc8 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,12 @@ +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 the latest BDA+). +- 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. diff --git a/CameraTools/Integration/BDArmory.cs b/CameraTools/Integration/BDArmory.cs index 73c4cbd8..deda1052 100644 --- a/CameraTools/Integration/BDArmory.cs +++ b/CameraTools/Integration/BDArmory.cs @@ -35,6 +35,7 @@ public List bdWMVessels #region Private fields CamTools camTools => CamTools.fetch; Vessel vessel => CamTools.fetch.vessel; + public Dictionary inputFields; object bdCompetitionInstance = null; Func bdCompetitionStartingFieldGetter = null; Func bdCompetitionIsActiveFieldGetter = null; @@ -52,10 +53,12 @@ public List bdWMVessels Func bdWmMissileFieldGetter = null; Func bdWmUnderFireFieldGetter = null; Func bdWmUnderAttackFieldGetter = null; + Func bdWmRecentlyFiringPropertyGetter = null; object bdLoadedVesselSwitcherInstance = null; Func bdLoadedVesselSwitcherVesselsPropertyGetter = null; Dictionary> bdActiveVessels = new Dictionary>(); float AItargetUpdateTime = 0; + [CTPersistantField] public float AItargetMinimumUpdateInterval = 3; Vessel newAITarget = null; List _bdWMVessels = new List(); float _bdWMVesselsLastUpdate = 0; @@ -78,12 +81,16 @@ void Start() GetMissileField(); GetUnderFireField(); GetUnderAttackField(); + GetRecentlyFiringProperty(); if (FlightGlobals.ActiveVessel is not null) { CheckForBDAI(FlightGlobals.ActiveVessel); CheckForBDWM(FlightGlobals.ActiveVessel); } } + inputFields = new Dictionary { + {"AItargetUpdateInterval", gameObject.AddComponent().Initialise(0, AItargetMinimumUpdateInterval, 0.5f, 5f, 4)}, + }; } void OnDestroy() @@ -242,11 +249,32 @@ Vessel GetAITargetedVessel() if (autoTargetIncomingMissiles && hasBDWM && wmComponent != null && bdWmMissileFieldGetter != null) { var missile = bdWmMissileFieldGetter(wmComponent); // Priority 1: incoming missiles. - if (missile != null) return missile; + if (missile != null) + { + if (CamTools.DEBUG && missile != camTools.dogfightTarget) CamTools.DebugLog($"Incoming missile {missile.vesselName}"); + return missile; + } } // Don't update too often unless there's no target. - if (camTools.dogfightTarget != null && Time.time - AItargetUpdateTime < 3) return camTools.dogfightTarget; + if (camTools.dogfightTarget != null && Time.time - AItargetUpdateTime < AItargetMinimumUpdateInterval) + return camTools.dogfightTarget; + + // Recently firing on a target. + Vessel target = null; + if (hasBDAI && aiComponent != null && bdAITargetFieldGetter != null) + { + target = bdAITargetFieldGetter(aiComponent); + } + if (target != null && hasBDWM && wmComponent != null && bdWmRecentlyFiringPropertyGetter != null) + { + bool recentlyFiring = (bool)bdWmRecentlyFiringPropertyGetter(wmComponent); // Priority 2: recently firing on a target. + if (recentlyFiring) + { + if (CamTools.DEBUG && target != camTools.dogfightTarget) CamTools.DebugLog($"Recently firing on {target.vesselName}"); + return target; + } + } // Threats if (hasBDWM && wmComponent != null && bdWmThreatFieldGetter != null) @@ -256,16 +284,20 @@ Vessel GetAITargetedVessel() if (underFire || underAttack) { - var threat = bdWmThreatFieldGetter(wmComponent); // Priority 2: incoming fire (can also be missiles). - if (threat != null) return threat; + var threat = bdWmThreatFieldGetter(wmComponent); // Priority 3: incoming fire (can also be missiles). + if (threat != null) + { + if (CamTools.DEBUG && threat != camTools.dogfightTarget) CamTools.DebugLog($"Incoming fire/missile {threat.vesselName}"); + return threat; + } } } // Targets - if (hasBDAI && aiComponent != null && bdAITargetFieldGetter != null) + if (target != null) { - var target = bdAITargetFieldGetter(aiComponent); // Priority 3: the current vessel's target. - if (target != null) return target; + if (CamTools.DEBUG && target != camTools.dogfightTarget) CamTools.DebugLog($"Targeting {target.vesselName}"); + return target; // Priority 4: the current vessel's target. } return null; } @@ -315,16 +347,22 @@ public void UpdateAIDogfightTarget() if (hasBDAI && hasBDWM && useBDAutoTarget) { newAITarget = GetAITargetedVessel(); - if (newAITarget != null && newAITarget != camTools.dogfightTarget) + if (newAITarget != null) { - if (CamTools.DEBUG) + if (newAITarget != camTools.dogfightTarget) + { + if (CamTools.DEBUG) + { + var message = "Switching dogfight target to " + newAITarget.vesselName + (camTools.dogfightTarget != null ? " from " + camTools.dogfightTarget.vesselName : ""); + CamTools.DebugLog(message); + } + camTools.dogfightTarget = newAITarget; + AItargetUpdateTime = Time.time; + } + else if (Time.time - AItargetUpdateTime >= AItargetMinimumUpdateInterval) { - var message = "Switching dogfight target to " + newAITarget.vesselName + (camTools.dogfightTarget != null ? " from " + camTools.dogfightTarget.vesselName : ""); - Debug.Log("[CameraTools]: " + message); - CamTools.DebugLog(message); + AItargetUpdateTime += 0.5f; // Delay the next update by 0.5s to avoid checking every frame once the minimum interval has elapsed. } - camTools.dogfightTarget = newAITarget; - AItargetUpdateTime = Time.time; } } } @@ -499,6 +537,23 @@ FieldInfo GetAITargetField() return null; } + PropertyInfo GetRecentlyFiringProperty() + { + Type wmModType = WeaponManagerType(); + if (wmModType == null) return null; + + PropertyInfo[] propertyInfos = wmModType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (var p in propertyInfos) + { + if (p != null && p.Name == "recentlyFiring") + { + bdWmRecentlyFiringPropertyGetter = ReflectionUtils.BuildGetAccessor(p.GetGetMethod()); + if (CamTools.DEBUG) Debug.Log("[CameraTools]: Created bdWmRecentlyFiringPropertyGetter."); + return p; + } + } + return null; + } public void GetBDVessels() { if (!hasBDA || bdLoadedVesselSwitcherVesselsPropertyGetter == null || bdLoadedVesselSwitcherInstance == null) return; @@ -525,5 +580,36 @@ public Vector3 GetCentroid() centroid /= (float)count; return centroid; } + + public void ToggleInputFields(bool textInput) + { + if (textInput) + { + foreach (var field in inputFields.Keys) + { + var fieldInfo = typeof(BDArmory).GetField(field); + if (fieldInfo != null) { inputFields[field].currentValue = (float)fieldInfo.GetValue(this); } + else + { + var propInfo = typeof(BDArmory).GetProperty(field); + inputFields[field].currentValue = (float)propInfo.GetValue(this); + } + } + } + else + { + foreach (var field in inputFields.Keys) + { + inputFields[field].tryParseValueNow(); + var fieldInfo = typeof(BDArmory).GetField(field); + if (fieldInfo != null) { fieldInfo.SetValue(this, inputFields[field].currentValue); } + else + { + var propInfo = typeof(BDArmory).GetProperty(field); + propInfo.SetValue(this, inputFields[field].currentValue); + } + } + } + } } } \ No newline at end of file diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index 02383d2c..eb5f6b41 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.24.0.0" )] -[assembly: AssemblyFileVersion( "1.24.0" )] +[assembly: AssemblyVersion( "1.25.0.0" )] +[assembly: AssemblyFileVersion( "1.25.0" )] From a215711f8d7a6569d2517f490819c6a0f67f52a4 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sat, 12 Nov 2022 21:41:21 +0100 Subject: [PATCH 148/263] Update changelog with specific BDA+ version number. --- CameraTools/CamTools.cs | 2 +- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 38983ab9..d6460239 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -2160,7 +2160,7 @@ void SetDoppler(bool includeActiveVessel) CTPartAudioController pa = audioSources[i].gameObject.GetComponent(); if (pa == null) pa = audioSources[i].gameObject.AddComponent(); pa.audioSource = audioSources[i]; - if (DEBUG && audioSources[i].isPlaying) Debug.Log($"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}"); + // if (DEBUG && audioSources[i].isPlaying) Debug.Log($"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}"); } } diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 94a37fc8..d6b71fe5 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -4,7 +4,7 @@ Improvements / Bugfixes: - 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 the latest BDA+). +- 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 From c109931ae3ff8a2827391779a336f6c4de171498 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Wed, 16 Nov 2022 00:16:39 +0100 Subject: [PATCH 149/263] Prevent the camera from going below the surface in dogfight mode when the vessel is landed or near the surface when splashed. --- CameraTools/CamTools.cs | 5 +++++ CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 3 +++ 2 files changed, 8 insertions(+) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index d6460239..30b19eca 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -1219,6 +1219,11 @@ void UpdateDogfightCamera() if (bdArmory.hasBDA && bdArmory.hasBDAI && bdArmory.useBDAutoTarget) { bdArmory.UpdateAIDogfightTarget(); // Using delegates instead of reflection allows us to check every frame. + if (vessel.LandedOrSplashed) + { + var cameraRadarAltitude = GetRadarAltitudeAtPos(flightCamera.transform.position); + if (cameraRadarAltitude < 2f && (vessel.Landed || cameraRadarAltitude > -dogfightDistance)) flightCamera.transform.position += (2f - cameraRadarAltitude) * cameraUp; // Prevent viewing from under the surface if near the surface. + } } if (dogfightTarget != dogfightPrevTarget) diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index d6b71fe5..bf8e4478 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,6 @@ +Improvements / Bugfixes: +- Prevent the camera from going below the surface in dogfight mode when the vessel is landed or near the surface when splashed. + v1.25.0 Improvements / Bugfixes: - Add an inertial chase mode to the dogfight camera. From e656b607c8a75fd11c7b8ec3e85fce921e64ecc4 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 22 Nov 2022 09:32:31 +0100 Subject: [PATCH 150/263] Update README --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index be466a3e..e5f34429 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -Camera Tools (forked by westamastaflash) +Camera Tools continued -Custom Camera angles for Cinematics! +Custom Camera angles for Cinematics and BDArmory! KSP Forum Thread: -http://forum.kerbalspaceprogram.com/index.php?/topic/84938-105-camera-tools-v151-dogfight-mode-autozoom-margin-slider-march-28/ - - +https://forum.kerbalspaceprogram.com/index.php?/topic/201063-camera-tools-continued-v1150/ From ed43ba975642a3a040a240ab7bb0a6f606402cb0 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 27 Nov 2022 22:48:51 +0100 Subject: [PATCH 151/263] Ignore ksp_dir.txt lines starting with # for post-build events in Linux --- CameraTools/CameraTools.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/CameraTools/CameraTools.csproj b/CameraTools/CameraTools.csproj index 96b5d841..98cc2da2 100644 --- a/CameraTools/CameraTools.csproj +++ b/CameraTools/CameraTools.csproj @@ -231,6 +231,7 @@ copy /Y "$(TargetDir)$(Targetname).pdb" "%25KSP_DIR%25\GameData\CameraTools\Plug 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" From 72b3859e20d62515e4ad06e0c4903b90f9c69704 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Wed, 30 Nov 2022 20:42:59 +0100 Subject: [PATCH 152/263] Disable roll and offset in dogfight mode when the vessel is an EVA kerbal. --- CameraTools/CamTools.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 30b19eca..c454ed67 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -1010,7 +1010,7 @@ void UpdateDogfightCamera() } //roll - if (dogfightRoll > 0) + if (dogfightRoll > 0 && !vessel.LandedOrSplashed && !vessel.isEVA) { var vesselRollTarget = Quaternion.RotateTowards(Quaternion.identity, Quaternion.FromToRotation(cameraUp, -vessel.ReferenceTransform.forward), dogfightRoll * Vector3.Angle(cameraUp, -vessel.ReferenceTransform.forward)); dogfightCameraRoll = Quaternion.Lerp(dogfightCameraRoll, vesselRollTarget, dogfightLerp); @@ -1023,7 +1023,8 @@ void UpdateDogfightCamera() Vector3 lagDirection = (dogfightLastTargetPosition - vessel.CoM).normalized; Vector3 offsetDirectionY = dogfightInertialChaseMode ? Quaternion.RotateTowards(Quaternion.identity, Quaternion.FromToRotation(cameraUp, -vessel.ReferenceTransform.forward), Vector3.Angle(cameraUp, -vessel.ReferenceTransform.forward)) * cameraUp : dogfightCameraRollUp; Vector3 offsetDirectionX = Vector3.Cross(offsetDirectionY, lagDirection).normalized; - Vector3 camPos = vessel.CoM + (-lagDirection * dogfightDistance) + (dogfightOffsetX * offsetDirectionX) + (dogfightOffsetY * offsetDirectionY); + Vector3 camPos = vessel.CoM + (-lagDirection * dogfightDistance); + if (!vessel.isEVA) camPos += (dogfightOffsetX * offsetDirectionX) + (dogfightOffsetY * offsetDirectionY); Vector3 localCamPos = cameraParent.transform.InverseTransformPoint(camPos); if (dogfightInertialChaseMode) From fe8b1585edfcc185392e178616c1c25322427032 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 4 Dec 2022 15:52:25 +0100 Subject: [PATCH 153/263] Update changelog --- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index bf8e4478..21518e6d 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,5 +1,7 @@ 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). v1.25.0 Improvements / Bugfixes: @@ -284,4 +286,4 @@ v1.1 v1.0 -- Initial release \ No newline at end of file +- Initial release From 55c30d5e582d993a33c73a1d2614fcec6fdecfe2 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 29 Jan 2023 16:13:35 +0100 Subject: [PATCH 154/263] Fix bug in field name for input boxes. Add DEBUG2 info for camera altitude. --- CameraTools/CamTools.cs | 6 ++- .../GameData/CameraTools/Changelog.txt | 1 + CameraTools/Integration/BDArmory.cs | 38 +++++++++++++------ 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index c454ed67..eb36dbeb 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -1224,7 +1224,9 @@ void UpdateDogfightCamera() { var cameraRadarAltitude = GetRadarAltitudeAtPos(flightCamera.transform.position); if (cameraRadarAltitude < 2f && (vessel.Landed || cameraRadarAltitude > -dogfightDistance)) flightCamera.transform.position += (2f - cameraRadarAltitude) * cameraUp; // Prevent viewing from under the surface if near the surface. + if (DEBUG2) Debug2Log($"camera altitude: {GetRadarAltitudeAtPos(flightCamera.transform.position):G3} ({cameraRadarAltitude:G3})"); } + else if (DEBUG2) Debug2Log($"vessel not landed"); } if (dogfightTarget != dogfightPrevTarget) @@ -2737,8 +2739,8 @@ void GuiWindow(int windowID) } else { - bdArmory.inputFields["AItargetUpdateInterval"].tryParseValue(GUI.TextField(RightRect(line), bdArmory.inputFields["AItargetUpdateInterval"].possibleValue, 8, inputFieldStyle)); - bdArmory.AItargetMinimumUpdateInterval = bdArmory.inputFields["AItargetUpdateInterval"].currentValue; + bdArmory.inputFields["AItargetMinimumUpdateInterval"].tryParseValue(GUI.TextField(RightRect(line), bdArmory.inputFields["AItargetMinimumUpdateInterval"].possibleValue, 8, inputFieldStyle)); + bdArmory.AItargetMinimumUpdateInterval = bdArmory.inputFields["AItargetMinimumUpdateInterval"].currentValue; } bdArmory.autoTargetIncomingMissiles = GUI.Toggle(ThinRect(++line), bdArmory.autoTargetIncomingMissiles, "Target Incoming Missiles"); } diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 21518e6d..41f68d09 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -2,6 +2,7 @@ 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: diff --git a/CameraTools/Integration/BDArmory.cs b/CameraTools/Integration/BDArmory.cs index deda1052..7593bd48 100644 --- a/CameraTools/Integration/BDArmory.cs +++ b/CameraTools/Integration/BDArmory.cs @@ -89,7 +89,7 @@ void Start() } } inputFields = new Dictionary { - {"AItargetUpdateInterval", gameObject.AddComponent().Initialise(0, AItargetMinimumUpdateInterval, 0.5f, 5f, 4)}, + {"AItargetMinimumUpdateInterval", gameObject.AddComponent().Initialise(0, AItargetMinimumUpdateInterval, 0.5f, 5f, 4)}, }; } @@ -587,12 +587,19 @@ public void ToggleInputFields(bool textInput) { foreach (var field in inputFields.Keys) { - var fieldInfo = typeof(BDArmory).GetField(field); - if (fieldInfo != null) { inputFields[field].currentValue = (float)fieldInfo.GetValue(this); } - else + try + { + var fieldInfo = typeof(BDArmory).GetField(field); + if (fieldInfo != null) { inputFields[field].currentValue = (float)fieldInfo.GetValue(this); } + else + { + var propInfo = typeof(BDArmory).GetProperty(field); + inputFields[field].currentValue = (float)propInfo.GetValue(this); + } + } + catch (Exception e) { - var propInfo = typeof(BDArmory).GetProperty(field); - inputFields[field].currentValue = (float)propInfo.GetValue(this); + Debug.LogError($"[CameraTools.BDArmory]: Failed to get field/property {field}: {e.Message}"); } } } @@ -600,13 +607,20 @@ public void ToggleInputFields(bool textInput) { foreach (var field in inputFields.Keys) { - inputFields[field].tryParseValueNow(); - var fieldInfo = typeof(BDArmory).GetField(field); - if (fieldInfo != null) { fieldInfo.SetValue(this, inputFields[field].currentValue); } - else + try + { + inputFields[field].tryParseValueNow(); + var fieldInfo = typeof(BDArmory).GetField(field); + if (fieldInfo != null) { fieldInfo.SetValue(this, inputFields[field].currentValue); } + else + { + var propInfo = typeof(BDArmory).GetProperty(field); + propInfo.SetValue(this, inputFields[field].currentValue); + } + } + catch (Exception e) { - var propInfo = typeof(BDArmory).GetProperty(field); - propInfo.SetValue(this, inputFields[field].currentValue); + Debug.LogError($"[CameraTools.BDArmory]: Failed to set field/property {field}: {e.Message}"); } } } From b364761e117031fac94638785c083cc956c34f4c Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Mon, 30 Jan 2023 00:21:35 +0100 Subject: [PATCH 155/263] Update changelog and bump version number for release. --- .../Distribution/GameData/CameraTools/CameraTools.version | 2 +- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 1 + CameraTools/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index 9d0bddab..80a352fb 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -5,7 +5,7 @@ "CHANGE_LOG_URL": "https://github.com/BrettRyland/CameraTools/blob/master/CameraTools/Distribution/GameData/CameraTools/Changelog.txt", "VERSION": { "MAJOR": 1, - "MINOR": 25, + "MINOR": 26, "PATCH": 0, "BUILD": 0 }, diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 41f68d09..9806f8b4 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,4 @@ +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. diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index eb5f6b41..6d993987 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.25.0.0" )] -[assembly: AssemblyFileVersion( "1.25.0" )] +[assembly: AssemblyVersion( "1.26.0.0" )] +[assembly: AssemblyFileVersion( "1.26.0" )] From bccad535776b16f8cad3693a8dedfb55bd53ad3c Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 3 Feb 2023 00:15:09 +0100 Subject: [PATCH 156/263] Fix dogfight centroid mode. --- CameraTools/CamTools.cs | 40 +++++++++++-------- .../GameData/CameraTools/Changelog.txt | 2 + CameraTools/Integration/BDArmory.cs | 16 ++++---- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index eb36dbeb..69d1bc37 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -21,7 +21,6 @@ public class CamTools : MonoBehaviour public Vessel vessel; List engines = new List(); List cockpits = new List(); - // List bdWMVessels; public static HashSet ignoreVesselTypesForAudio = new HashSet { 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; @@ -900,7 +899,12 @@ void StartDogfightCamera() } if (DEBUG) { Debug.Log("[CameraTools]: Starting dogfight camera."); DebugLog("Starting dogfight camera"); } - if (dogfightTarget) + if (bdArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 1) + { + dogfightLastTarget = true; + dogfightVelocityChase = false; + } + else if (dogfightTarget) { dogfightVelocityChase = false; } @@ -968,21 +972,21 @@ void UpdateDogfightCamera() { return; } } - if (dogfightTarget) + if (DEBUG2 && Time.deltaTime > 0) debug2Messages.Clear(); + + if (bdArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 1) + { + dogfightLastTarget = true; + dogfightLastTargetVelocity = Vector3.zero; + dogfightLastTargetPosition = bdArmory.GetCentroid(); + if (DEBUG2) Debug2Log($"Centroid: {dogfightLastTargetPosition:G3}"); + } + else if (dogfightTarget) { if (loadedVessels == null) UpdateLoadedVessels(); - if (bdArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 2) - { - dogfightLastTarget = true; - dogfightLastTargetVelocity = Vector3.zero; - dogfightLastTargetPosition = bdArmory.GetCentroid(); - } - else - { - dogfightLastTarget = true; - dogfightLastTargetPosition = dogfightTarget.CoM; - dogfightLastTargetVelocity = dogfightTarget.Velocity(); - } + dogfightLastTarget = true; + dogfightLastTargetPosition = dogfightTarget.CoM; + dogfightLastTargetVelocity = dogfightTarget.Velocity(); } else if (dogfightLastTarget) { @@ -1042,7 +1046,6 @@ void UpdateDogfightCamera() } if (DEBUG2 && Time.deltaTime > 0) { - debug2Messages.Clear(); 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("G3")); @@ -1217,7 +1220,7 @@ void UpdateDogfightCamera() } } - if (bdArmory.hasBDA && bdArmory.hasBDAI && bdArmory.useBDAutoTarget) + if (bdArmory.hasBDA && bdArmory.hasBDAI && (bdArmory.useBDAutoTarget || (bdArmory.useCentroid && bdArmory.bdWMVessels.Count < 2))) { bdArmory.UpdateAIDogfightTarget(); // Using delegates instead of reflection allows us to check every frame. if (vessel.LandedOrSplashed) @@ -2323,6 +2326,7 @@ public void RevertCamera() flightCamera.SetFoV(origFov); currentFOV = origFov; cameraParentWasStolen = false; + dogfightLastTarget = false; } if (HighLogic.LoadedSceneIsFlight) flightCamera.mainCamera.nearClipPlane = origNearClip; @@ -2696,6 +2700,8 @@ void GuiWindow(int windowID) string tVesselLabel; if (showingVesselList) { tVesselLabel = "Clear"; } + else if (bdArmory.hasBDA && bdArmory.useCentroid) + { tVesselLabel = "Centroid"; } else if (dogfightTarget) { tVesselLabel = dogfightTarget.vesselName; } else diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 9806f8b4..a822f59c 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,5 @@ +- Fix dogfight centroid 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. diff --git a/CameraTools/Integration/BDArmory.cs b/CameraTools/Integration/BDArmory.cs index 7593bd48..f05d4490 100644 --- a/CameraTools/Integration/BDArmory.cs +++ b/CameraTools/Integration/BDArmory.cs @@ -344,7 +344,7 @@ Type WeaponManagerType() public void UpdateAIDogfightTarget() { - if (hasBDAI && hasBDWM && useBDAutoTarget) + if (hasBDAI && hasBDWM && (useBDAutoTarget || (useCentroid && bdWMVessels.Count < 2))) { newAITarget = GetAITargetedVessel(); if (newAITarget != null) @@ -564,18 +564,16 @@ public void GetBDVessels() public Vector3 GetCentroid() { - Vector3 centroid = Vector3.zero; + var activeVesselCoM = FlightGlobals.ActiveVessel.CoM; + Vector3 centroid = activeVesselCoM; int count = 1; foreach (var v in bdWMVessels) { - if (v == null || !v.loaded || v.packed) continue; - if ((v.CoM - FlightGlobals.ActiveVessel.CoM).magnitude > 20000) continue; - if (!v.isActiveVessel) - { - centroid += v.transform.position; - ++count; - } + if (v == null || !v.loaded || v.packed || v.isActiveVessel) continue; + if ((v.CoM - activeVesselCoM).magnitude > 20000) continue; + centroid += v.transform.position; + ++count; } centroid /= (float)count; return centroid; From 9f78ad922383df214c7287ae2fb4dfe81dfef3d6 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sat, 18 Feb 2023 00:48:23 +0100 Subject: [PATCH 157/263] 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. --- CameraTools/CamTools.cs | 66 +++++++++++++------ .../GameData/CameraTools/Changelog.txt | 2 + CameraTools/Integration/BDArmory.cs | 30 ++++++++- 3 files changed, 76 insertions(+), 22 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 69d1bc37..23c7376b 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -790,7 +790,7 @@ void FixedUpdate() // Updating of the stationary camera is handled in Update. if (fmMode == fmModeTypes.Speed) { - manualPosition += cameraUp * fmSpeeds.y + forwardAxis * fmSpeeds.z + flightCamera.transform.right * fmSpeeds.x; + manualPosition += upAxis * fmSpeeds.y + forwardAxis * fmSpeeds.z + rightAxis * fmSpeeds.x; if (!autoFOV) { zoomExp = Mathf.Clamp(zoomExp + fmSpeeds.w, 1f, 8f); @@ -1014,7 +1014,7 @@ void UpdateDogfightCamera() } //roll - if (dogfightRoll > 0 && !vessel.LandedOrSplashed && !vessel.isEVA) + if (dogfightRoll > 0 && !vessel.LandedOrSplashed && !vessel.isEVA && !bdArmory.isBDMissile) { var vesselRollTarget = Quaternion.RotateTowards(Quaternion.identity, Quaternion.FromToRotation(cameraUp, -vessel.ReferenceTransform.forward), dogfightRoll * Vector3.Angle(cameraUp, -vessel.ReferenceTransform.forward)); dogfightCameraRoll = Quaternion.Lerp(dogfightCameraRoll, vesselRollTarget, dogfightLerp); @@ -1240,6 +1240,7 @@ void UpdateDogfightCamera() #endregion #region Stationary Camera + Quaternion stationaryCameraRoll = Quaternion.identity; void StartStationaryCamera() { toolMode = ToolModes.StationaryCamera; @@ -1259,6 +1260,7 @@ void StartStationaryCamera() cameraUp = Vector3.up; } rightAxis = -Vector3.Cross(vessel.Velocity(), vessel.upAxis).normalized; + stationaryCameraRoll = Quaternion.identity; if (flightCamera.transform.parent != cameraParent.transform) { @@ -1430,6 +1432,19 @@ void UpdateStationaryCamera() if (flightCamera.Target != null) flightCamera.SetTargetNone(); // Don't go to the next vessel if the vessel is destroyed. + if (Input.GetKey(fmMovementModifier)) + { + upAxis = flightCamera.transform.up; + forwardAxis = flightCamera.transform.forward; + rightAxis = flightCamera.transform.right; + } + else + { + upAxis = stationaryCameraRoll * cameraUp; + forwardAxis = Vector3.RotateTowards(upAxis, flightCamera.transform.forward, Mathf.Deg2Rad * 90, 0).normalized; + rightAxis = Vector3.Cross(upAxis, forwardAxis); + } + // Set camera position before rotation to avoid jitter. if (vessel != null) { @@ -1457,12 +1472,12 @@ void UpdateStationaryCamera() lookPosition = camTarget.vessel.CoM; } - flightCamera.transform.rotation = Quaternion.LookRotation(lookPosition - flightCamera.transform.position, cameraUp); + flightCamera.transform.rotation = Quaternion.LookRotation(lookPosition - flightCamera.transform.position, upAxis); lastTargetPosition = lookPosition; } else if (hasTarget) { - flightCamera.transform.rotation = Quaternion.LookRotation(lastTargetPosition - flightCamera.transform.position, cameraUp); + flightCamera.transform.rotation = Quaternion.LookRotation(lastTargetPosition - flightCamera.transform.position, upAxis); } if (DEBUG2 && !GameIsPaused) @@ -1492,10 +1507,6 @@ void UpdateStationaryCamera() lastCamParentPosition = cameraParent.transform.position; } - //mouse panning, moving - forwardAxis = (Quaternion.AngleAxis(-90, cameraUp) * flightCamera.transform.right).normalized; // FIXME Why are these calculated like this? They ought to either use pre-existing axes or cross products. - rightAxis = (Quaternion.AngleAxis(90, forwardAxis) * cameraUp).normalized; - //free move if (enableKeypad && !boundThisFrame) { @@ -1505,11 +1516,11 @@ void UpdateStationaryCamera() { if (Input.GetKey(fmUpKey)) { - manualPosition += cameraUp * freeMoveSpeed * Time.fixedDeltaTime; + manualPosition += upAxis * freeMoveSpeed * Time.fixedDeltaTime; } else if (Input.GetKey(fmDownKey)) { - manualPosition -= cameraUp * freeMoveSpeed * Time.fixedDeltaTime; + manualPosition -= upAxis * freeMoveSpeed * Time.fixedDeltaTime; } if (Input.GetKey(fmForwardKey)) { @@ -1521,11 +1532,11 @@ void UpdateStationaryCamera() } if (Input.GetKey(fmLeftKey)) { - manualPosition -= flightCamera.transform.right * freeMoveSpeed * Time.fixedDeltaTime; + manualPosition -= rightAxis * freeMoveSpeed * Time.fixedDeltaTime; } else if (Input.GetKey(fmRightKey)) { - manualPosition += flightCamera.transform.right * freeMoveSpeed * Time.fixedDeltaTime; + manualPosition += rightAxis * freeMoveSpeed * Time.fixedDeltaTime; } //keyZoom @@ -1596,18 +1607,26 @@ void UpdateStationaryCamera() } } - if (camTarget == null && Input.GetKey(KeyCode.Mouse1)) + if (Input.GetKey(KeyCode.Mouse1) && Input.GetKey(KeyCode.Mouse2)) { - 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); + stationaryCameraRoll = Quaternion.AngleAxis(Input.GetAxis("Mouse X") * -1.7f, flightCamera.transform.forward) * stationaryCameraRoll; + flightCamera.transform.rotation = Quaternion.LookRotation(flightCamera.transform.forward, stationaryCameraRoll * cameraUp); } - if (Input.GetKey(KeyCode.Mouse2)) + else { - manualPosition += flightCamera.transform.right * Input.GetAxis("Mouse X") * 2; - manualPosition += forwardAxis * Input.GetAxis("Mouse Y") * 2; + if (camTarget == null && Input.GetKey(KeyCode.Mouse1)) + { + 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, stationaryCameraRoll * cameraUp); + } + if (Input.GetKey(KeyCode.Mouse2)) + { + manualPosition += rightAxis * Input.GetAxis("Mouse X") * 2; + manualPosition += forwardAxis * Input.GetAxis("Mouse Y") * 2; + } } - manualPosition += cameraUp * 10 * Input.GetAxis("Mouse ScrollWheel"); + manualPosition += upAxis * 10 * Input.GetAxis("Mouse ScrollWheel"); //autoFov if (camTarget != null && autoFOV) @@ -1812,7 +1831,6 @@ void UpdatePathingCam() { flightCamera.transform.rotation *= Quaternion.AngleAxis(Input.GetAxis("Mouse X") * 1.7f / (zoomExp * zoomExp), Vector3.up); 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)) // Middle: move left/right and forward/backward { @@ -2227,6 +2245,7 @@ void SwitchToVessel(Vessel v) { bdArmory.CheckForBDAI(v); bdArmory.CheckForBDWM(v); + if (!bdArmory.hasBDAI) bdArmory.CheckForBDMissile(FlightGlobals.ActiveVessel); bdArmory.UpdateAIDogfightTarget(); } if (cameraToolActive) @@ -2239,6 +2258,11 @@ void SwitchToVessel(Vessel v) { StartStationaryCamera(); } + else if (bdArmory.hasBDA && bdArmory.isBDMissile) + { + dogfightTarget = null; + StartDogfightCamera(); // Use dogfight chase mode for BDA missiles. + } else { ChooseRandomMode(); diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index a822f59c..fe4aadc6 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,4 +1,6 @@ - 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. v1.26.0 Improvements / Bugfixes: diff --git a/CameraTools/Integration/BDArmory.cs b/CameraTools/Integration/BDArmory.cs index f05d4490..dd593e4c 100644 --- a/CameraTools/Integration/BDArmory.cs +++ b/CameraTools/Integration/BDArmory.cs @@ -22,6 +22,7 @@ public class BDArmory : MonoBehaviour public bool autoEnableOverride = false; public bool hasBDAI = false; public bool hasPilotAI = false; + public bool isBDMissile = false; public List bdWMVessels { get @@ -86,6 +87,7 @@ void Start() { CheckForBDAI(FlightGlobals.ActiveVessel); CheckForBDWM(FlightGlobals.ActiveVessel); + if (!hasBDAI) CheckForBDMissile(FlightGlobals.ActiveVessel); } } inputFields = new Dictionary { @@ -194,11 +196,12 @@ public void CheckForBDAI(Vessel v) { hasBDAI = false; hasPilotAI = false; + isBDMissile = false; aiComponent = null; if (v) { foreach (Part p in v.parts) - { // We actually want BDGenericAIBase, but we can't use GetComponent(string) to find it, so we look for its subclasses. + { // We actually want BDGenericAIBase, but we can't use GetComponent(string) to find it (since it's abstract), so we look for its subclasses. if (p.GetComponent("BDModulePilotAI")) { hasBDAI = true; @@ -224,6 +227,21 @@ public void CheckForBDAI(Vessel v) } } + public void CheckForBDMissile(Vessel v) + { + isBDMissile = false; + if (hasBDAI || hasBDWM) return; // If it has a WM or AI, then it's not a missile and we shouldn't even be checking. + if (v == null) return; // If the vessel is null, it's not a missile. + foreach (var p in v.parts) + { // We actually want MissileBase, but we can't use GetComponent(string) to find it (since it's abstract), so we look for its subclasses. + if (p.GetComponent("MissileLauncher") || p.GetComponent("BDModularGuidance")) + { + isBDMissile = true; + return; + } + } + } + public bool CheckForBDWM(Vessel v) { hasBDWM = false; @@ -365,6 +383,16 @@ public void UpdateAIDogfightTarget() } } } + else if (isBDMissile && useBDAutoTarget) + { + camTools.dogfightTarget = null; + AItargetUpdateTime = Time.time; + if (CamTools.DEBUG) + { + var message = "Current vessel is a missile, using dogfight chase mode."; + CamTools.DebugLog(message); + } + } } public void AutoEnableForBDA() From cba087410b45f750a45aa3276ee6a27b44d88fbd Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Mon, 13 Mar 2023 00:44:50 +0100 Subject: [PATCH 158/263] Add support for using the MouseAimFlight in dogfight camera mode. Optimise access to flight camera transform in dogfight camera mode. --- CameraTools/CamTools.cs | 87 +++++++---- CameraTools/CameraTools.csproj | 7 +- .../GameData/CameraTools/Changelog.txt | 1 + .../BDArmory.cs | 2 +- .../BetterTimeWarp.cs | 2 +- CameraTools/ModIntegration/MouseAimFlight.cs | 137 ++++++++++++++++++ .../TimeControl.cs | 2 +- 7 files changed, 202 insertions(+), 36 deletions(-) rename CameraTools/{Integration => ModIntegration}/BDArmory.cs (99%) rename CameraTools/{Integration => ModIntegration}/BetterTimeWarp.cs (98%) create mode 100644 CameraTools/ModIntegration/MouseAimFlight.cs rename CameraTools/{Integration => ModIntegration}/TimeControl.cs (98%) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 23c7376b..718a9458 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -6,7 +6,7 @@ using System; using UnityEngine; -using CameraTools.Integration; +using CameraTools.ModIntegration; namespace CameraTools { @@ -214,6 +214,8 @@ public float autoZoomMargin Vector3 dogfightLastTargetVelocity; bool dogfightVelocityChase = false; bool cockpitView = false; + Vector3 mouseAimFlightTarget = default; + Vector3 mouseAimFlightTargetLocal = default; #endregion #region Stationary Camera Fields @@ -319,10 +321,10 @@ float pathTime [CTPersistantField] public bool useRealTime = true; #endregion - #region Integration + #region Mod Integration BDArmory bdArmory; - TimeControl timeControl; BetterTimeWarp betterTimeWarp; + TimeControl timeControl; #endregion #endregion @@ -366,8 +368,8 @@ void Start() deathCam = new GameObject("CameraToolsDeathCam"); bdArmory = BDArmory.instance; - timeControl = TimeControl.instance; betterTimeWarp = BetterTimeWarp.instance; + timeControl = TimeControl.instance; if (FlightGlobals.ActiveVessel != null) { @@ -502,7 +504,7 @@ void KrakensbaneWarpCorrection() else if (!inHighWarp && useObtVel != wasUsingObtVel) // Only needed when crossing the boundary. floatingKrakenAdjustment += ((useObtVel ? vessel.obt_velocity : vessel.srf_velocity) - Krakensbane.GetFrameVelocity()) * TimeWarp.fixedDeltaTime; cameraParent.transform.position += floatingKrakenAdjustment; - if (DEBUG2) + if (DEBUG2 && !GameIsPaused) { var cmb = FlightGlobals.currentMainBody; Debug2Log("situation: " + vessel.situation); @@ -731,6 +733,7 @@ void FixedUpdate() // 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 (DEBUG2 && !GameIsPaused) debug2Messages.Clear(); if (cameraToolActive) { @@ -899,7 +902,13 @@ void StartDogfightCamera() } if (DEBUG) { Debug.Log("[CameraTools]: Starting dogfight camera."); DebugLog("Starting dogfight camera"); } - if (bdArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 1) + if (MouseAimFlight.IsMouseAimActive()) + { + dogfightTarget = null; + dogfightLastTarget = true; + dogfightVelocityChase = false; + } + else if (bdArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 1) { dogfightLastTarget = true; dogfightVelocityChase = false; @@ -972,14 +981,23 @@ void UpdateDogfightCamera() { return; } } - if (DEBUG2 && Time.deltaTime > 0) debug2Messages.Clear(); - - if (bdArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 1) + 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 (bdArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 1) { dogfightLastTarget = true; dogfightLastTargetVelocity = Vector3.zero; dogfightLastTargetPosition = bdArmory.GetCentroid(); - if (DEBUG2) Debug2Log($"Centroid: {dogfightLastTargetPosition:G3}"); + if (DEBUG2 && !GameIsPaused) Debug2Log($"Centroid: {dogfightLastTargetPosition:G3}"); } else if (dogfightTarget) { @@ -1001,11 +1019,11 @@ void UpdateDogfightCamera() var lastDogfightLastTargetPosition = dogfightLastTargetPosition; if (vessel.Speed() > 1) { - dogfightLastTargetPosition = vessel.CoM + vessel.Velocity().normalized * 5000; + dogfightLastTargetPosition = vessel.CoM + vessel.Velocity().normalized * 5000f; } else { - dogfightLastTargetPosition = vessel.CoM + vessel.ReferenceTransform.up * 5000; + dogfightLastTargetPosition = vessel.CoM + vessel.ReferenceTransform.up * 5000f; } if (vessel.Splashed && vessel.Speed() < 10) // Don't bob around lots if the vessel is in water. { @@ -1035,16 +1053,16 @@ void UpdateDogfightCamera() { dogfightLerpMomentum /= dogfightLerpMomentum.sqrMagnitude * 2f / dogfightDistance + 1f; dogfightLerpMomentum += dogfightLerpDelta * dogfightInertialFactor; - dogfightLerpDelta = -flightCamera.transform.localPosition; + dogfightLerpDelta = -cameraTransform.localPosition; } - flightCamera.transform.localPosition = Vector3.Lerp(flightCamera.transform.localPosition, localCamPos, dogfightLerp); + cameraTransform.localPosition = Vector3.Lerp(cameraTransform.localPosition, localCamPos, dogfightLerp); if (dogfightInertialChaseMode) { - flightCamera.transform.localPosition += dogfightLerpMomentum; - dogfightLerpDelta += flightCamera.transform.localPosition; + cameraTransform.localPosition += dogfightLerpMomentum; + dogfightLerpDelta += cameraTransform.localPosition; if (dogfightLerpDelta.sqrMagnitude > dogfightDistance * dogfightDistance) dogfightLerpDelta *= dogfightDistance / dogfightLerpDelta.magnitude; } - if (DEBUG2 && Time.deltaTime > 0) + if (DEBUG2 && !GameIsPaused) { Debug2Log("time scale: " + Time.timeScale.ToString("G3") + ", Δt: " + Time.fixedDeltaTime.ToString("G3")); Debug2Log("offsetDirection: " + offsetDirectionX.ToString("G3")); @@ -1052,16 +1070,24 @@ void UpdateDogfightCamera() 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") + ", " + flightCamera.transform.localPosition.ToString("G3")); + Debug2Log("localCamPos: " + localCamPos.ToString("G3") + ", " + cameraTransform.localPosition.ToString("G3")); Debug2Log($"lerp momentum: {dogfightLerpMomentum:G3}"); Debug2Log($"lerp delta: {dogfightLerpDelta:G3}"); } //rotation - Quaternion vesselLook = Quaternion.LookRotation(vessel.CoM - flightCamera.transform.position, dogfightCameraRollUp); - Quaternion targetLook = Quaternion.LookRotation(dogfightLastTargetPosition - flightCamera.transform.position, dogfightCameraRollUp); + Quaternion vesselLook = Quaternion.LookRotation(vessel.CoM - cameraTransform.position, dogfightCameraRollUp); + Quaternion targetLook = Quaternion.LookRotation(dogfightLastTargetPosition - cameraTransform.position, dogfightCameraRollUp); Quaternion camRot = Quaternion.Lerp(vesselLook, targetLook, 0.5f); - flightCamera.transform.rotation = Quaternion.Lerp(flightCamera.transform.rotation, camRot, dogfightLerp); + cameraTransform.rotation = Quaternion.Lerp(cameraTransform.rotation, camRot, dogfightLerp); + if (MouseAimFlight.IsMouseAimActive()) + { + // 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. + } //autoFov if (autoFOV) @@ -1073,7 +1099,7 @@ void UpdateDogfightCamera() } else { - float angle = Vector3.Angle(dogfightLastTargetPosition - flightCamera.transform.position, vessel.CoM - flightCamera.transform.position); + float angle = Vector3.Angle(dogfightLastTargetPosition - cameraTransform.position, vessel.CoM - cameraTransform.position); targetFoV = Mathf.Clamp(angle + autoZoomMargin, 0.1f, 60f); } manualFOV = targetFoV; @@ -1225,11 +1251,11 @@ void UpdateDogfightCamera() bdArmory.UpdateAIDogfightTarget(); // Using delegates instead of reflection allows us to check every frame. if (vessel.LandedOrSplashed) { - var cameraRadarAltitude = GetRadarAltitudeAtPos(flightCamera.transform.position); - if (cameraRadarAltitude < 2f && (vessel.Landed || cameraRadarAltitude > -dogfightDistance)) flightCamera.transform.position += (2f - cameraRadarAltitude) * cameraUp; // Prevent viewing from under the surface if near the surface. - if (DEBUG2) Debug2Log($"camera altitude: {GetRadarAltitudeAtPos(flightCamera.transform.position):G3} ({cameraRadarAltitude:G3})"); + var cameraRadarAltitude = GetRadarAltitudeAtPos(cameraTransform.position); + if (cameraRadarAltitude < 2f && (vessel.Landed || cameraRadarAltitude > -dogfightDistance)) cameraTransform.position += (2f - cameraRadarAltitude) * cameraUp; // Prevent viewing from under the surface if near the surface. + if (DEBUG2 && !GameIsPaused) Debug2Log($"camera altitude: {GetRadarAltitudeAtPos(cameraTransform.position):G3} ({cameraRadarAltitude:G3})"); } - else if (DEBUG2) Debug2Log($"vessel not landed"); + else if (DEBUG2 && !GameIsPaused) Debug2Log($"vessel not landed"); } if (dogfightTarget != dogfightPrevTarget) @@ -1422,8 +1448,6 @@ bool GetAutoLandingPosition() Vector3 lastCamParentPosition = Vector3.zero; void UpdateStationaryCamera() { - if (DEBUG2 && !GameIsPaused) - debug2Messages.Clear(); if (useAudioEffects) { speedOfSound = 233 * MathUtils.Sqrt(1 + (FlightGlobals.getExternalTemperature(vessel.GetWorldPos3D(), vessel.mainBody) / 273.15)); @@ -2452,7 +2476,7 @@ void OnGUI() { if (debug2Messages.Count > 0) { - GUI.Label(new Rect(Screen.width - 750, 100, 700, 400), string.Join("\n", debug2Messages.Select(m => m.Item1.ToString("0.000") + " " + m.Item2))); + 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))); } } } @@ -2722,7 +2746,9 @@ void GuiWindow(int windowID) { GUI.Label(ThinRect(++line), "Secondary Target:"); string tVesselLabel; - if (showingVesselList) + if (MouseAimFlight.IsMouseAimActive()) + { tVesselLabel = "MouseAimFlight"; } + else if (showingVesselList) { tVesselLabel = "Clear"; } else if (bdArmory.hasBDA && bdArmory.useCentroid) { tVesselLabel = "Centroid"; } @@ -2745,6 +2771,7 @@ void GuiWindow(int windowID) } if (showingVesselList) { + if (MouseAimFlight.IsMouseAimActive()) showingVesselList = false; foreach (var v in loadedVessels) { if (!v || !v.loaded) continue; diff --git a/CameraTools/CameraTools.csproj b/CameraTools/CameraTools.csproj index 98cc2da2..e59bf692 100644 --- a/CameraTools/CameraTools.csproj +++ b/CameraTools/CameraTools.csproj @@ -69,9 +69,10 @@ - - - + + + + diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index fe4aadc6..52bd2268 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,6 +1,7 @@ - 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 in dogfight camera mode. v1.26.0 Improvements / Bugfixes: diff --git a/CameraTools/Integration/BDArmory.cs b/CameraTools/ModIntegration/BDArmory.cs similarity index 99% rename from CameraTools/Integration/BDArmory.cs rename to CameraTools/ModIntegration/BDArmory.cs index dd593e4c..3d7f83ff 100644 --- a/CameraTools/Integration/BDArmory.cs +++ b/CameraTools/ModIntegration/BDArmory.cs @@ -6,7 +6,7 @@ using UnityEngine; -namespace CameraTools.Integration +namespace CameraTools.ModIntegration { [KSPAddon(KSPAddon.Startup.Flight, false)] public class BDArmory : MonoBehaviour diff --git a/CameraTools/Integration/BetterTimeWarp.cs b/CameraTools/ModIntegration/BetterTimeWarp.cs similarity index 98% rename from CameraTools/Integration/BetterTimeWarp.cs rename to CameraTools/ModIntegration/BetterTimeWarp.cs index aaac1258..9969c0ac 100644 --- a/CameraTools/Integration/BetterTimeWarp.cs +++ b/CameraTools/ModIntegration/BetterTimeWarp.cs @@ -2,7 +2,7 @@ using System; using System.Reflection; -namespace CameraTools.Integration +namespace CameraTools.ModIntegration { [KSPAddon(KSPAddon.Startup.Flight, false)] public class BetterTimeWarp : MonoBehaviour diff --git a/CameraTools/ModIntegration/MouseAimFlight.cs b/CameraTools/ModIntegration/MouseAimFlight.cs new file mode 100644 index 00000000..9ef484b7 --- /dev/null +++ b/CameraTools/ModIntegration/MouseAimFlight.cs @@ -0,0 +1,137 @@ +using UnityEngine; +using System; +using System.Reflection; + +namespace CameraTools.ModIntegration +{ + [KSPAddon(KSPAddon.Startup.Flight, false)] + public class MouseAimFlight : MonoBehaviour + { + public static MouseAimFlight Instance; + public static bool hasMouseAimFlight = false; + + Type mouseAimFlightType = null; + object mouseAimFlightInstance = null; + Func mouseAimActiveFieldGetter = null; + Func targetPositionFieldGetter = null; + Action targetPositionFieldSetter = null; + bool mouseAimActive = false; + float lastChecked = 0; + Vessel activeVessel = null; + Vector3 lastTarget = default; + + void Awake() + { + if (Instance is not null) Destroy(Instance); + Instance = this; + } + + void Start() + { + FindMouseAimFlight(); + if (hasMouseAimFlight) + { + if (CamTools.DEBUG) Debug.Log($"[CameraTools.ModIntegration.MouseAimFlight]: MouseAimFlight mod detected."); + FindMouseAimFlightModule(); + } + else + { + Destroy(this); // Destroy ourselves to not take up any further CPU cycles. + } + } + + void FindMouseAimFlight() + { + try + { + bool foundMouseAimActive = false; + bool foundMouseAimTarget = false; + foreach (var assy in AssemblyLoader.loadedAssemblies) + { + if (assy.assembly.FullName.Contains("MouseAimFlight")) + { + foreach (var type in assy.assembly.GetTypes()) + { + if (type == null) continue; + if (type.Name == "MouseAimVesselModule") + { + hasMouseAimFlight = true; + mouseAimFlightType = type; + foreach (var fieldInfo in mouseAimFlightType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)) + { + if (fieldInfo == null) continue; + if (fieldInfo.Name == "mouseAimActive") + { + mouseAimActiveFieldGetter = ReflectionUtils.CreateGetter(fieldInfo); + foundMouseAimActive = true; + } + else if (fieldInfo.Name == "targetPosition") + { + targetPositionFieldGetter = ReflectionUtils.CreateGetter(fieldInfo); + targetPositionFieldSetter = ReflectionUtils.CreateSetter(fieldInfo); + foundMouseAimTarget = true; + } + if (foundMouseAimActive && foundMouseAimTarget) return; + } + } + } + } + } + if (hasMouseAimFlight && (!foundMouseAimActive || !foundMouseAimTarget)) + { + Debug.LogWarning($"[CameraTools.ModIntegration.MouseAimFlight]: MouseAimFlight mod found, but failed to locate the required fields: mouseAimActive: {foundMouseAimActive}, : targetPosition: {foundMouseAimTarget}"); + hasMouseAimFlight = false; + } + } + catch (Exception e) + { + Debug.LogError($"[CameraTools.ModIntegration.MouseAimFlight]: Failed to locate mouseAimActive in MouseAimFlight module: {e.Message}"); + hasMouseAimFlight = false; + Destroy(this); + } + } + + void FindMouseAimFlightModule() + { + mouseAimFlightInstance = null; + activeVessel = FlightGlobals.ActiveVessel; + lastChecked = 0; + if (!hasMouseAimFlight || activeVessel == null) return; + mouseAimFlightInstance = (object)activeVessel.GetComponent(mouseAimFlightType); + if (CamTools.DEBUG) Debug.Log($"[CameraTools.Integration.MouseAimFlight]: Mouse Aim Flight module {(mouseAimFlightInstance != null ? "" : "not ")}found on {activeVessel.vesselName}"); + } + + bool CheckMouseAimActive() + { + lastChecked = Time.realtimeSinceStartup; + if (FlightGlobals.ActiveVessel != activeVessel) FindMouseAimFlightModule(); + if (mouseAimFlightInstance == null) return false; + return mouseAimActiveFieldGetter(mouseAimFlightInstance); + } + + public bool IsMouseAimFlightActive() + { + if (!hasMouseAimFlight) return false; + if (FlightGlobals.ActiveVessel != activeVessel) FindMouseAimFlightModule(); + if (Time.realtimeSinceStartup - lastChecked > 1f) mouseAimActive = CheckMouseAimActive(); // Only check at most once per second unless a vessel switch occurs. + return mouseAimActive; + } + + public Vector3 GetCurrentMouseAimTarget() + { + if (!IsMouseAimActive()) return lastTarget; + lastTarget = targetPositionFieldGetter(mouseAimFlightInstance); + return lastTarget; + } + + public void SetCurrentMouseAimTarget(Vector3 position) + { + if (!IsMouseAimActive()) return; + targetPositionFieldSetter(mouseAimFlightInstance, position); + } + + public static bool IsMouseAimActive() => hasMouseAimFlight && Instance != null && Instance.IsMouseAimFlightActive(); + public static Vector3 GetMouseAimTarget() => Instance.GetCurrentMouseAimTarget(); + public static void SetMouseAimTarget(Vector3 position) => Instance.SetCurrentMouseAimTarget(position); + } +} \ No newline at end of file diff --git a/CameraTools/Integration/TimeControl.cs b/CameraTools/ModIntegration/TimeControl.cs similarity index 98% rename from CameraTools/Integration/TimeControl.cs rename to CameraTools/ModIntegration/TimeControl.cs index f64ff82a..fa046ce9 100644 --- a/CameraTools/Integration/TimeControl.cs +++ b/CameraTools/ModIntegration/TimeControl.cs @@ -2,7 +2,7 @@ using System; using System.Reflection; -namespace CameraTools.Integration +namespace CameraTools.ModIntegration { [KSPAddon(KSPAddon.Startup.Flight, false)] public class TimeControl : MonoBehaviour From f7f3507a01fc99f3355e4b9ad33b6b1f2ffdc136 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 19 Mar 2023 15:30:09 +0100 Subject: [PATCH 159/263] Delay camera activation/deactivation when in map mode until back in flight mode. --- CameraTools/CamTools.cs | 28 ++++++++++++++++--- .../GameData/CameraTools/Changelog.txt | 1 + CameraTools/ModIntegration/MouseAimFlight.cs | 2 +- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 718a9458..4f7d7cf6 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -37,6 +37,7 @@ public class CamTools : MonoBehaviour 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). System.Random rng; Vessel.Situations lastVesselSituation = Vessel.Situations.FLYING; [CTPersistantField] public static bool DEBUG = false; @@ -452,18 +453,27 @@ void CameraModeChange(CameraManager.CameraMode mode) { wasActiveBeforeModeChange = cameraToolActive; cameraToolActive = false; + Debug.Log($"[CameraTools]: Deactivating due to switching to {mode} camera mode."); } else if (mode == CameraManager.CameraMode.Flight) { if (wasActiveBeforeModeChange && !autoEnableOverriden && !bdArmory.autoEnableOverride) { - Debug.Log("[CameraTools]: Camera mode changed to " + mode + ", reactivating " + toolMode + "."); + Debug.Log($"[CameraTools]: Camera mode changed to {mode}, 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 = deathCam.transform.position; flightCamera.transform.rotation = deathCam.transform.rotation; - cameraActivate(); + if (!revertWhenInFlightMode) + cameraActivate(); + } + else if (revertWhenInFlightMode) + { + Debug.Log($"[CameraTools]: Camera mode changed to {mode}, applying delayed revert."); + cockpitView = false; // Don't go back into cockpit view in case it was triggered by the user. + cameraToolActive = true; + RevertCamera(); } } } @@ -666,6 +676,9 @@ void Update() } } } + boundThisFrame = false; + + if (MapView.MapIsEnabled) return; // Don't do anything else in map mode. if (Input.GetMouseButtonUp(0)) { @@ -725,7 +738,6 @@ void Update() break; } } - boundThisFrame = false; } void FixedUpdate() @@ -733,6 +745,7 @@ void FixedUpdate() // 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 (DEBUG2 && !GameIsPaused) debug2Messages.Clear(); if (cameraToolActive) @@ -864,6 +877,7 @@ void LateUpdate() public void cameraActivate() { + if (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Flight) return; // Don't activate if we're not in Flight mode. if (DEBUG) { Debug.Log("[CameraTools]: Activating camera."); DebugLog("Activating camera"); } if (!cameraToolActive) { @@ -2326,17 +2340,23 @@ void ChooseRandomMode() 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; + return; + } if (DEBUG) { message = "Reverting camera."; Debug.Log("[CameraTools]: " + message); DebugLog(message); } - if (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Flight) + 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; } + revertWhenInFlightMode = false; if (cameraToolActive) { diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 52bd2268..0298d78c 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -2,6 +2,7 @@ - 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 in dogfight camera mode. +- Delay camera activation/deactivation when in map mode until back in flight mode. v1.26.0 Improvements / Bugfixes: diff --git a/CameraTools/ModIntegration/MouseAimFlight.cs b/CameraTools/ModIntegration/MouseAimFlight.cs index 9ef484b7..8068e51e 100644 --- a/CameraTools/ModIntegration/MouseAimFlight.cs +++ b/CameraTools/ModIntegration/MouseAimFlight.cs @@ -98,7 +98,7 @@ void FindMouseAimFlightModule() lastChecked = 0; if (!hasMouseAimFlight || activeVessel == null) return; mouseAimFlightInstance = (object)activeVessel.GetComponent(mouseAimFlightType); - if (CamTools.DEBUG) Debug.Log($"[CameraTools.Integration.MouseAimFlight]: Mouse Aim Flight module {(mouseAimFlightInstance != null ? "" : "not ")}found on {activeVessel.vesselName}"); + if (CamTools.DEBUG) Debug.Log($"[CameraTools.ModIntegration.MouseAimFlight]: Mouse Aim Flight module {(mouseAimFlightInstance != null ? "" : "not ")}found on {activeVessel.vesselName}"); } bool CheckMouseAimActive() From 02f1e7af34f5dbb75a12ee7980f0567f9ddedb31 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 19 Mar 2023 15:32:11 +0100 Subject: [PATCH 160/263] Bump version numbers for release. --- .../Distribution/GameData/CameraTools/CameraTools.version | 4 ++-- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 1 + CameraTools/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index 80a352fb..830e7576 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -5,14 +5,14 @@ "CHANGE_LOG_URL": "https://github.com/BrettRyland/CameraTools/blob/master/CameraTools/Distribution/GameData/CameraTools/Changelog.txt", "VERSION": { "MAJOR": 1, - "MINOR": 26, + "MINOR": 27, "PATCH": 0, "BUILD": 0 }, "KSP_VERSION": { "MAJOR": 1, "MINOR": 12, - "PATCH": 3 + "PATCH": 5 }, "KSP_VERSION_MIN": { "MAJOR": 1, diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 0298d78c..7bb351a7 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,4 @@ +v1.27.0 - 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. diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index 6d993987..fe55ef64 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.26.0.0" )] -[assembly: AssemblyFileVersion( "1.26.0" )] +[assembly: AssemblyVersion( "1.27.0.0" )] +[assembly: AssemblyFileVersion( "1.27.0" )] From 015d6f342c2b92fbb8b541a338ba3984707f7b19 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 19 Mar 2023 15:47:38 +0100 Subject: [PATCH 161/263] Add delayed activation too. --- CameraTools/CamTools.cs | 14 +++++++++++--- .../GameData/CameraTools/Changelog.txt | 4 ++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 4f7d7cf6..13d6f1cc 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -38,6 +38,7 @@ public class CamTools : MonoBehaviour 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; @@ -457,7 +458,7 @@ void CameraModeChange(CameraManager.CameraMode mode) } else if (mode == CameraManager.CameraMode.Flight) { - if (wasActiveBeforeModeChange && !autoEnableOverriden && !bdArmory.autoEnableOverride) + if ((wasActiveBeforeModeChange || activateWhenInFlightMode) && !autoEnableOverriden && !bdArmory.autoEnableOverride) { Debug.Log($"[CameraTools]: Camera mode changed to {mode}, reactivating {toolMode}."); cockpitView = false; // Don't go back into cockpit view in case it was triggered by the user. @@ -877,7 +878,13 @@ void LateUpdate() public void cameraActivate() { - if (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Flight) return; // Don't activate if we're not in Flight mode. + if (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Flight) + { + activateWhenInFlightMode = true; + revertWhenInFlightMode = false; + return; // Don't activate if we're not in Flight mode. + } + activateWhenInFlightMode = false; if (DEBUG) { Debug.Log("[CameraTools]: Activating camera."); DebugLog("Activating camera"); } if (!cameraToolActive) { @@ -2343,8 +2350,10 @@ 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."; @@ -2356,7 +2365,6 @@ public void RevertCamera() CameraManager.Instance.SetCameraFlight(); cameraToolActive = true; } - revertWhenInFlightMode = false; if (cameraToolActive) { diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 7bb351a7..b0d7167e 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -2,8 +2,8 @@ v1.27.0 - 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 in dogfight camera mode. -- Delay camera activation/deactivation when in map mode until back in flight mode. +- 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: From 2e611d0dfa4e113be4db76673bbac308ee2d7a9f Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 19 Mar 2023 15:54:11 +0100 Subject: [PATCH 162/263] Update changelog --- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index b0d7167e..74c5d235 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,4 +1,5 @@ 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. From a4d936cecce89018a503a9222c9e8eeac150ab5f Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Wed, 5 Apr 2023 13:43:00 +0200 Subject: [PATCH 163/263] Adjust random mode selection for low altitude to be more amenable to pod-racing. --- CameraTools/CamTools.cs | 2 +- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 13d6f1cc..31aec8a5 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -2297,7 +2297,7 @@ void SwitchToVessel(Vessel v) { if (randomMode) { - var lowAlt = Math.Max(30d, -3d * vessel.verticalSpeed); // 30m or 3s to impact, whichever is higher. + var lowAlt = Math.Max(30d, -5d * vessel.verticalSpeed * Mathf.Max(0, Vector3.Dot(vessel.srf_vel_direction, -cameraUp))); // 30m or up to 5s to impact (depending on angle), whichever is higher. var stationarySurfaceVessel = (vessel.Landed && vessel.Speed() < 1) || (vessel.Splashed && vessel.Speed() < 5); // Land or water vessel that isn't moving much. if (stationarySurfaceVessel || (bdArmory.hasPilotAI && vessel.radarAltitude < lowAlt)) { diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 74c5d235..128635f1 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,6 @@ +Improvements / Bugfixes: +- Adjust random mode selection for low altitude to be more amenable to pod-racing. + v1.27.0 Improvements / Bugfixes: - Fix dogfight centroid mode. From 7e448f6c880e910c5fe5d6b628cf971fb28f103a Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Mon, 22 May 2023 10:12:21 +0200 Subject: [PATCH 164/263] Add a visual toggle for free-move mode (speed vs position). --- CameraTools/CamTools.cs | 12 ++++++++---- .../Distribution/GameData/CameraTools/Changelog.txt | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 31aec8a5..1c2bfbbe 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -2525,10 +2525,10 @@ 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) - { return new Rect(leftIndent + contentWidth - 25f, contentTop + line * entryHeight, 25f, entryHeight); } - Rect SliderRect(float line, float indent) - { return new Rect(leftIndent + indent, contentTop + line * entryHeight + 6f, contentWidth - indent - 30f, entryHeight); } + Rect SliderLabelRight(float line, float widthAdjust = 0) + { return new Rect(leftIndent + contentWidth - 25f - widthAdjust, contentTop + line * entryHeight, 25f + widthAdjust, entryHeight); } + Rect SliderRect(float line, float indent, float widthAdjust = 0) + { return new Rect(leftIndent + indent, contentTop + line * entryHeight + 6f, contentWidth - indent - 30f + widthAdjust, entryHeight); } Rect RightSliderRect(float line) { return new Rect(windowWidth / 2f + 3f * leftIndent, contentTop + line * entryHeight + 6f, contentWidth / 2f - 3f * leftIndent, entryHeight); } void SetupInputFieldStyle() @@ -3162,6 +3162,10 @@ void GuiWindow(int windowID) inputFields["keyZoomSpeed"].tryParseValue(GUI.TextField(RightRect(line), inputFields["keyZoomSpeed"].possibleValue, 8, inputFieldStyle)); keyZoomSpeed = inputFields["keyZoomSpeed"].currentValue; } + + GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), "Mode:"); + fmMode = (fmModeTypes)Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, contentWidth / 2f - 30f, -30f), (int)fmMode, 0, 1)); + GUI.Label(SliderLabelRight(line, 30f), fmMode.ToString()); } line++; diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 128635f1..a5c97d5a 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,5 +1,6 @@ Improvements / Bugfixes: - Adjust random mode selection for low altitude to be more amenable to pod-racing. +- Add a visual toggle for free-move mode (speed vs position). v1.27.0 Improvements / Bugfixes: From 6e7afef0c0e3112438bb337d72fc3a33a24f7033 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Mon, 22 May 2023 11:20:32 +0200 Subject: [PATCH 165/263] 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. --- CameraTools/CamTools.cs | 10 +++++++++- .../Distribution/GameData/CameraTools/Changelog.txt | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 1c2bfbbe..f3477154 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -68,6 +68,7 @@ public class CamTools : MonoBehaviour [CTPersistantField] public string fmZoomOutKey = "[3]"; [CTPersistantField] public string fmMovementModifier = "enter"; [CTPersistantField] public string fmModeToggleKey = "[2]"; + [CTPersistantField] public string resetRollKey; bool waitingForTarget = false; bool waitingForPosition = false; bool mouseUp = false; @@ -677,7 +678,6 @@ void Update() } } } - boundThisFrame = false; if (MapView.MapIsEnabled) return; // Don't do anything else in map mode. @@ -844,6 +844,7 @@ void FixedUpdate() void LateUpdate() { + boundThisFrame = false; UpdateCameraShake(); // Update camera shake each frame so that it dies down. if (hasDied && cameraToolActive) { @@ -1650,6 +1651,12 @@ void UpdateStationaryCamera() } break; } + + if (!string.IsNullOrEmpty(resetRollKey) && Input.GetKeyDown(resetRollKey)) + { + stationaryCameraRoll = Quaternion.identity; + flightCamera.transform.rotation = Quaternion.LookRotation(flightCamera.transform.forward, stationaryCameraRoll * cameraUp); + } } if (Input.GetKey(KeyCode.Mouse1) && Input.GetKey(KeyCode.Mouse2)) @@ -3187,6 +3194,7 @@ void GuiWindow(int windowID) fmZoomOutKey = KeyBinding(fmZoomOutKey, "Zoom Out", ++line); fmMovementModifier = KeyBinding(fmMovementModifier, "Modifier", ++line); fmModeToggleKey = KeyBinding(fmModeToggleKey, "FM Mode", ++line); + resetRollKey = KeyBinding(resetRollKey, "Reset Roll", ++line); } Rect saveRect = HalfRect(++line, 0); diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index a5c97d5a..a37266a8 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,6 +1,7 @@ Improvements / Bugfixes: - Adjust random mode selection for low altitude to be more amenable to 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. v1.27.0 Improvements / Bugfixes: From 0c8216f72c066d71b14d7e3a1e1afa40b6fa5a5e Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Mon, 22 May 2023 22:28:14 +0200 Subject: [PATCH 166/263] Use empty string, not null for unassigned key. --- CameraTools/CamTools.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index f3477154..70801933 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -68,7 +68,7 @@ public class CamTools : MonoBehaviour [CTPersistantField] public string fmZoomOutKey = "[3]"; [CTPersistantField] public string fmMovementModifier = "enter"; [CTPersistantField] public string fmModeToggleKey = "[2]"; - [CTPersistantField] public string resetRollKey; + [CTPersistantField] public string resetRollKey = ""; bool waitingForTarget = false; bool waitingForPosition = false; bool mouseUp = false; From 9918e84b5eea11b14b8d01a8406c082167961727 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 28 May 2023 14:53:09 +0200 Subject: [PATCH 167/263] Fix camera position in Stationary and Dogfight modes when returning from Map mode. --- CameraTools/CamTools.cs | 27 ++++++++++++++++--- .../GameData/CameraTools/Changelog.txt | 3 ++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 70801933..6f7d24be 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -1,8 +1,8 @@ using KSP.UI.Screens; using System.Collections.Generic; -using System.Linq; +using System.Collections; using System.IO; -using System.Reflection; +using System.Linq; using System; using UnityEngine; @@ -455,7 +455,7 @@ void CameraModeChange(CameraManager.CameraMode mode) { wasActiveBeforeModeChange = cameraToolActive; cameraToolActive = false; - Debug.Log($"[CameraTools]: Deactivating due to switching to {mode} camera mode."); + if (wasActiveBeforeModeChange) Debug.Log($"[CameraTools]: Deactivating due to switching to {mode} camera mode."); } else if (mode == CameraManager.CameraMode.Flight) { @@ -468,7 +468,10 @@ void CameraModeChange(CameraManager.CameraMode mode) flightCamera.transform.position = deathCam.transform.position; flightCamera.transform.rotation = deathCam.transform.rotation; if (!revertWhenInFlightMode) - cameraActivate(); + { + 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(); + } } else if (revertWhenInFlightMode) { @@ -480,6 +483,21 @@ void CameraModeChange(CameraManager.CameraMode mode) } } + 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; @@ -3548,6 +3566,7 @@ public void SetZoomImmediate(float zoom) void SetCameraParent(Transform referenceTransform, bool resetToCoM = false) { + if (DEBUG) Debug.Log($"[CameraTools]: Setting the camera parent to {referenceTransform.gameObject.name} from {flightCamera.gameObject.name}, reset-to-CoM: {resetToCoM}"); cameraParent.transform.position = referenceTransform.position; cameraParent.transform.rotation = referenceTransform.rotation; flightCamera.SetTargetNone(); diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index a37266a8..e26b32d7 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,7 +1,8 @@ Improvements / Bugfixes: -- Adjust random mode selection for low altitude to be more amenable to pod-racing. +- 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. v1.27.0 Improvements / Bugfixes: From 0646979b1cc152cab8ab4079daab2d05c05299b4 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 28 May 2023 16:12:06 +0200 Subject: [PATCH 168/263] Add a free-look mode to Dogfight mode (hold right mouse button). --- CameraTools/CamTools.cs | 38 ++++++++++++++----- .../GameData/CameraTools/Changelog.txt | 1 + CameraTools/ModIntegration/BDArmory.cs | 16 ++++---- CameraTools/ModIntegration/MouseAimFlight.cs | 5 +++ 4 files changed, 41 insertions(+), 19 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 6f7d24be..d12636e2 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -53,6 +53,8 @@ public class CamTools : MonoBehaviour Vector3 forwardAxis; Vector3 rightAxis; + bool freeLook = false; + #region Input [CTPersistantField] public string cameraKey = "home"; [CTPersistantField] public string revertKey = "end"; @@ -909,6 +911,7 @@ public void cameraActivate() { timeControl.SetTimeControlCameraZoomFix(false); betterTimeWarp.SetBetterTimeWarpScaleCameraSpeed(false); + freeLook = false; } if (!cameraToolActive && !cameraParentWasStolen) { @@ -1116,17 +1119,32 @@ void UpdateDogfightCamera() } //rotation - Quaternion vesselLook = Quaternion.LookRotation(vessel.CoM - cameraTransform.position, dogfightCameraRollUp); - Quaternion targetLook = Quaternion.LookRotation(dogfightLastTargetPosition - cameraTransform.position, dogfightCameraRollUp); - Quaternion camRot = Quaternion.Lerp(vesselLook, targetLook, 0.5f); - cameraTransform.rotation = Quaternion.Lerp(cameraTransform.rotation, camRot, dogfightLerp); - if (MouseAimFlight.IsMouseAimActive()) + if (Input.GetKey(KeyCode.Mouse1)) // Free-look + { + freeLook = true; + cameraTransform.rotation *= Quaternion.AngleAxis(Input.GetAxis("Mouse X") * 1.7f, Vector3.up); + cameraTransform.rotation *= Quaternion.AngleAxis(-Input.GetAxis("Mouse Y") * 1.7f, Vector3.right); + cameraTransform.rotation = Quaternion.LookRotation(cameraTransform.forward, dogfightCameraRollUp); + } + else { - // 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. + Quaternion vesselLook = Quaternion.LookRotation(vessel.CoM - cameraTransform.position, dogfightCameraRollUp); + Quaternion targetLook = Quaternion.LookRotation(dogfightLastTargetPosition - cameraTransform.position, dogfightCameraRollUp); + Quaternion camRot = Quaternion.Lerp(vesselLook, targetLook, 0.5f); + cameraTransform.rotation = Quaternion.Lerp(cameraTransform.rotation, camRot, dogfightLerp); + if (MouseAimFlight.IsMouseAimActive()) + { + if (freeLook) MouseAimFlight.SetFreeLookCooldown(1); // Give it 1s for the camera orientation to recover before resuming applying our modification to the MouseAimFlight target. + 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. + } + } + freeLook = false; } //autoFov diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index e26b32d7..c58f4311 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -3,6 +3,7 @@ Improvements / Bugfixes: - 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). v1.27.0 Improvements / Bugfixes: diff --git a/CameraTools/ModIntegration/BDArmory.cs b/CameraTools/ModIntegration/BDArmory.cs index 3d7f83ff..7791c682 100644 --- a/CameraTools/ModIntegration/BDArmory.cs +++ b/CameraTools/ModIntegration/BDArmory.cs @@ -47,9 +47,11 @@ public List bdWMVessels object bdBDATournamentInstance = null; Func bdTournamentWarpInProgressFieldGetter = null; bool hasBDWM = false; + Type aiModType = null; object aiComponent = null; - object wmComponent = null; Func bdAITargetFieldGetter = null; + Type wmModType = null; + object wmComponent = null; Func bdWmThreatFieldGetter = null; Func bdWmMissileFieldGetter = null; Func bdWmUnderFireFieldGetter = null; @@ -77,6 +79,8 @@ void Start() CheckForBDA(); if (hasBDA) { + aiModType = GetAIModuleType(); + wmModType = GetWeaponManagerType(); GetAITargetField(); GetThreatField(); GetMissileField(); @@ -320,7 +324,7 @@ Vessel GetAITargetedVessel() return null; } - Type AIModuleType() + Type GetAIModuleType() { foreach (var assy in AssemblyLoader.loadedAssemblies) { @@ -340,7 +344,7 @@ Type AIModuleType() return null; } - Type WeaponManagerType() + Type GetWeaponManagerType() { foreach (var assy in AssemblyLoader.loadedAssemblies) { @@ -472,7 +476,6 @@ public void AutoEnableForBDA() FieldInfo GetThreatField() { - Type wmModType = WeaponManagerType(); if (wmModType == null) return null; FieldInfo[] fields = wmModType.GetFields(BindingFlags.Public | BindingFlags.Instance); @@ -491,7 +494,6 @@ FieldInfo GetThreatField() FieldInfo GetMissileField() { - Type wmModType = WeaponManagerType(); if (wmModType == null) return null; FieldInfo[] fields = wmModType.GetFields(BindingFlags.Public | BindingFlags.Instance); @@ -510,7 +512,6 @@ FieldInfo GetMissileField() FieldInfo GetUnderFireField() { - Type wmModType = WeaponManagerType(); if (wmModType == null) return null; FieldInfo[] fields = wmModType.GetFields(BindingFlags.Public | BindingFlags.Instance); @@ -529,7 +530,6 @@ FieldInfo GetUnderFireField() FieldInfo GetUnderAttackField() { - Type wmModType = WeaponManagerType(); if (wmModType == null) return null; FieldInfo[] fields = wmModType.GetFields(BindingFlags.Public | BindingFlags.Instance); @@ -548,7 +548,6 @@ FieldInfo GetUnderAttackField() FieldInfo GetAITargetField() { - Type aiModType = AIModuleType(); if (aiModType == null) return null; FieldInfo[] fields = aiModType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); @@ -567,7 +566,6 @@ FieldInfo GetAITargetField() PropertyInfo GetRecentlyFiringProperty() { - Type wmModType = WeaponManagerType(); if (wmModType == null) return null; PropertyInfo[] propertyInfos = wmModType.GetProperties(BindingFlags.Public | BindingFlags.Instance); diff --git a/CameraTools/ModIntegration/MouseAimFlight.cs b/CameraTools/ModIntegration/MouseAimFlight.cs index 8068e51e..2c4f42e1 100644 --- a/CameraTools/ModIntegration/MouseAimFlight.cs +++ b/CameraTools/ModIntegration/MouseAimFlight.cs @@ -19,6 +19,7 @@ public class MouseAimFlight : MonoBehaviour float lastChecked = 0; Vessel activeVessel = null; Vector3 lastTarget = default; + float freeLookRecovery = 0; void Awake() { @@ -133,5 +134,9 @@ public void SetCurrentMouseAimTarget(Vector3 position) public static bool IsMouseAimActive() => hasMouseAimFlight && Instance != null && Instance.IsMouseAimFlightActive(); public static Vector3 GetMouseAimTarget() => Instance.GetCurrentMouseAimTarget(); public static void SetMouseAimTarget(Vector3 position) => Instance.SetCurrentMouseAimTarget(position); + + // Free Look + public static void SetFreeLookCooldown(float recoveryPeriod) => Instance.freeLookRecovery = Time.time + recoveryPeriod; + public static bool IsInFreeLookRecovery => Time.time < Instance.freeLookRecovery; } } \ No newline at end of file From 877fb2903f2542877025bea98bd17892b876c8a6 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 28 May 2023 16:14:42 +0200 Subject: [PATCH 169/263] Bump version number for release. --- .../Distribution/GameData/CameraTools/CameraTools.version | 2 +- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 3 ++- CameraTools/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index 830e7576..ec684b00 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -5,7 +5,7 @@ "CHANGE_LOG_URL": "https://github.com/BrettRyland/CameraTools/blob/master/CameraTools/Distribution/GameData/CameraTools/Changelog.txt", "VERSION": { "MAJOR": 1, - "MINOR": 27, + "MINOR": 28, "PATCH": 0, "BUILD": 0 }, diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index c58f4311..33510010 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,9 +1,10 @@ +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). +- Add a free-look mode to Dogfight mode (hold right mouse button, compatible with MouseAimFlight integration). v1.27.0 Improvements / Bugfixes: diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index fe55ef64..e2ac7768 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.27.0.0" )] -[assembly: AssemblyFileVersion( "1.27.0" )] +[assembly: AssemblyVersion( "1.28.0.0" )] +[assembly: AssemblyFileVersion( "1.28.0" )] From 15ba0aedad35830fec121a194c2ad6e459deefd2 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 6 Jun 2023 14:25:02 +0200 Subject: [PATCH 170/263] Add a configurable delay to activating free-look mode (default: 0.1s). --- CameraTools/CamTools.cs | 19 ++++++++++++++++--- .../GameData/CameraTools/Changelog.txt | 3 +++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index d12636e2..63ff8308 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -54,6 +54,8 @@ public class CamTools : MonoBehaviour Vector3 rightAxis; bool freeLook = false; + float freeLookTimer = 0; + [CTPersistantField] public float freeLookDelay = 0.1f; #region Input [CTPersistantField] public string cameraKey = "home"; @@ -1121,7 +1123,20 @@ void UpdateDogfightCamera() //rotation if (Input.GetKey(KeyCode.Mouse1)) // Free-look { - freeLook = true; + if (freeLookTimer >= freeLookDelay) freeLook = true; // Use a delay-timer to avoid activating on single right-clicks. + else freeLookTimer += Time.fixedDeltaTime; + } + else + { + freeLookTimer = 0; + if (freeLook) + { + 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 (freeLook) + { cameraTransform.rotation *= Quaternion.AngleAxis(Input.GetAxis("Mouse X") * 1.7f, Vector3.up); cameraTransform.rotation *= Quaternion.AngleAxis(-Input.GetAxis("Mouse Y") * 1.7f, Vector3.right); cameraTransform.rotation = Quaternion.LookRotation(cameraTransform.forward, dogfightCameraRollUp); @@ -1134,7 +1149,6 @@ void UpdateDogfightCamera() cameraTransform.rotation = Quaternion.Lerp(cameraTransform.rotation, camRot, dogfightLerp); if (MouseAimFlight.IsMouseAimActive()) { - if (freeLook) MouseAimFlight.SetFreeLookCooldown(1); // Give it 1s for the camera orientation to recover before resuming applying our modification to the MouseAimFlight target. if (!MouseAimFlight.IsInFreeLookRecovery) { // mouseAimFlightTarget keeps the target stationary (i.e., no change from the default) @@ -1144,7 +1158,6 @@ void UpdateDogfightCamera() MouseAimFlight.SetMouseAimTarget(newMouseAimFlightTarget); // Adjust how MouseAimFlight updates the target position for easier control in combat. } } - freeLook = false; } //autoFov diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 33510010..e5798c56 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,6 @@ +Improvements / Bugfixes: +- Add a configurable delay to activating free-look mode (default: 0.1s). + v1.28.0 Improvements / Bugfixes: - Adjust random mode selection for low altitude to be more amenable to BDA pod-racing. From 15766ae25c51b9749f2245d537a5754d237c2803 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 27 Jul 2023 20:57:44 +0200 Subject: [PATCH 171/263] Add a configurable movement threshold to activating free-look mode (default: 0.1). --- CameraTools/CamTools.cs | 31 +++++++++++++++---- .../GameData/CameraTools/Changelog.txt | 2 +- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 63ff8308..07b3bdbe 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -54,8 +54,8 @@ public class CamTools : MonoBehaviour Vector3 rightAxis; bool freeLook = false; - float freeLookTimer = 0; - [CTPersistantField] public float freeLookDelay = 0.1f; + Vector2 freeLookStartUpDistance = Vector2.zero; + [CTPersistantField] public float freeLookThresholdSqr = 0.1f; // Mouse movement threshold for starting free look (units unknown). #region Input [CTPersistantField] public string cameraKey = "home"; @@ -437,6 +437,7 @@ void Start() {"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)}, }; } @@ -914,6 +915,7 @@ public void cameraActivate() timeControl.SetTimeControlCameraZoomFix(false); betterTimeWarp.SetBetterTimeWarpScaleCameraSpeed(false); freeLook = false; + freeLookStartUpDistance = Vector2.zero; } if (!cameraToolActive && !cameraParentWasStolen) { @@ -1123,12 +1125,17 @@ void UpdateDogfightCamera() //rotation if (Input.GetKey(KeyCode.Mouse1)) // Free-look { - if (freeLookTimer >= freeLookDelay) freeLook = true; // Use a delay-timer to avoid activating on single right-clicks. - else freeLookTimer += Time.fixedDeltaTime; + if (!freeLook) + { + freeLookStartUpDistance.x += Input.GetAxis("Mouse X"); + freeLookStartUpDistance.y += -Input.GetAxis("Mouse Y"); + if (freeLookStartUpDistance.sqrMagnitude > freeLookThresholdSqr) + freeLook = true; + } } else { - freeLookTimer = 0; + freeLookStartUpDistance = Vector2.zero; if (freeLook) { freeLook = false; @@ -2729,7 +2736,7 @@ void GuiWindow(int windowID) inputFields["shakeMultiplier"].tryParseValue(GUI.TextField(RightRect(line), inputFields["shakeMultiplier"].possibleValue, 8, inputFieldStyle)); shakeMultiplier = inputFields["shakeMultiplier"].currentValue; } - line++; + ++line; //Stationary camera GUI if (toolMode == ToolModes.StationaryCamera) @@ -2941,6 +2948,18 @@ void GuiWindow(int windowID) dogfightRoll = inputFields["dogfightRoll"].currentValue; } + GUI.Label(SliderLabelLeft(++line, 95f), $"Free-Look Thr.:"); + 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(SliderLabelLeft(++line, 95f), $"Camera Inertia:"); if (!textInput) { diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index e5798c56..b72dc670 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,5 +1,5 @@ Improvements / Bugfixes: -- Add a configurable delay to activating free-look mode (default: 0.1s). +- Add a configurable movement threshold to activating free-look mode (default: 0.1). v1.28.0 Improvements / Bugfixes: From 1d3f4f01ba27ef384140b2037b2296091e5da842 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 4 Aug 2023 01:09:26 +0200 Subject: [PATCH 172/263] 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.). --- CameraTools/CamTools.cs | 22 ++++++++++++++----- .../GameData/CameraTools/Changelog.txt | 1 + 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 07b3bdbe..4194ac71 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -776,12 +776,22 @@ void FixedUpdate() { if ((!hasDied && flightCamera.transform.parent != cameraParent.transform) || (hasDied && flightCamera.transform.parent != deathCam.transform)) { - message = "Someone has stolen the camera parent! Abort!"; - Debug.Log("[CameraTools]: " + message); - if (DEBUG) DebugLog(message); - cameraToolActive = false; - cameraParentWasStolen = true; - RevertCamera(); + 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 = hasDied ? deathCam.transform : 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! Abort!"; + Debug.Log("[CameraTools]: " + message); + if (DEBUG) DebugLog(message); + cameraToolActive = false; + cameraParentWasStolen = true; + RevertCamera(); + } } } diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index b72dc670..89f23656 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,5 +1,6 @@ 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.). v1.28.0 Improvements / Bugfixes: From 36ef963f3260b863e21fb0c44a8462029f135fb8 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 8 Aug 2023 01:21:06 +0200 Subject: [PATCH 173/263] Allow customising the maximum value of the zoom, auto zoom, dogfight distance and dogfight offsets in the settings.cfg. --- CameraTools/CamTools.cs | 75 ++++++++++--------- .../GameData/CameraTools/Changelog.txt | 1 + 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 4194ac71..6643f8cb 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -177,9 +177,10 @@ enum fmModeTypes { Position, Speed }; Vessel dogfightPrevTarget; public Vessel dogfightTarget; [CTPersistantField] public float dogfightDistance = 30f; + [CTPersistantField] public float dogfightMaxDistance = 100; [CTPersistantField] public float dogfightOffsetX = 10f; [CTPersistantField] public float dogfightOffsetY = 4f; - float dogfightMaxOffset = 50; + [CTPersistantField] public float dogfightMaxOffset = 50; [CTPersistantField] public bool dogfightInertialChaseMode = false; [CTPersistantField] public float dogfightLerp = 0.2f; [CTPersistantField] public float dogfightRoll = 0f; @@ -190,6 +191,7 @@ enum fmModeTypes { Position, Speed }; Vector3 dogfightCameraRollUp = Vector3.up; [CTPersistantField] public float autoZoomMarginDogfight = 20; [CTPersistantField] public float autoZoomMarginStationary = 20; + [CTPersistantField] public float autoZoomMarginMax = 50f; public float autoZoomMargin { get @@ -251,6 +253,8 @@ public float autoZoomMargin [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 @@ -419,10 +423,10 @@ void Start() contentWidth = (windowWidth) - (2 * leftIndent); inputFields = new Dictionary { - {"autoZoomMargin", gameObject.AddComponent().Initialise(0, autoZoomMargin, 0f, 50f, 4)}, - {"zoomFactor", gameObject.AddComponent().Initialise(0, zoomFactor, 1f, 1096.63f, 4)}, + {"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, 100f, 3)}, + {"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)}, @@ -822,17 +826,17 @@ void FixedUpdate() 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, 100f); - if (dogfightDistance <= 1f || dogfightDistance >= 100f) fmSpeeds.z = 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, 8); - if (zoomExp <= 1 || zoomExp >= 8) fmSpeeds.w = 0; + 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, 50); - if (autoZoomMargin <= 0 || autoZoomMargin >= 50) fmSpeeds.w = 0; + autoZoomMargin = Mathf.Clamp(autoZoomMargin + 10 * fmSpeeds.w, 0, autoZoomMarginMax); + if (autoZoomMargin <= 0 || autoZoomMargin >= autoZoomMarginMax) fmSpeeds.w = 0; } } break; @@ -843,13 +847,13 @@ void FixedUpdate() manualPosition += upAxis * fmSpeeds.y + forwardAxis * fmSpeeds.z + rightAxis * fmSpeeds.x; if (!autoFOV) { - zoomExp = Mathf.Clamp(zoomExp + fmSpeeds.w, 1f, 8f); - if (zoomExp <= 1f || zoomExp >= 8f) fmSpeeds.w = 0; + 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, 50f); - if (autoZoomMargin <= 0f || autoZoomMargin >= 50f) fmSpeeds.w = 0; + autoZoomMargin = Mathf.Clamp(autoZoomMargin + 10 * fmSpeeds.w, 0f, autoZoomMarginMax); + if (autoZoomMargin <= 0f || autoZoomMargin >= autoZoomMarginMax) fmSpeeds.w = 0; } } break; @@ -858,8 +862,8 @@ void FixedUpdate() 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, 8f); - if (zoomExp <= 1f || zoomExp >= 8f) fmSpeeds.w = 0; + zoomExp = Mathf.Clamp(zoomExp + fmSpeeds.w, 1f, zoomMaxExp); + if (zoomExp <= 1f || zoomExp >= zoomMaxExp) fmSpeeds.w = 0; } break; default: @@ -1123,7 +1127,7 @@ void UpdateDogfightCamera() { 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("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")); @@ -1234,13 +1238,13 @@ void UpdateDogfightCamera() if (Input.GetKey(fmForwardKey)) { dogfightDistance -= freeMoveSpeed * Time.fixedDeltaTime; - dogfightDistance = Mathf.Clamp(dogfightDistance, 1f, 100f); + 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, 100f); + dogfightDistance = Mathf.Clamp(dogfightDistance, 1f, dogfightMaxDistance); if (textInput) inputFields["dogfightDistance"].currentValue = dogfightDistance; } if (Input.GetKey(fmLeftKey)) @@ -1261,12 +1265,12 @@ void UpdateDogfightCamera() { if (Input.GetKey(fmZoomInKey)) { - zoomExp = Mathf.Clamp(zoomExp + (keyZoomSpeed * Time.fixedDeltaTime), 1, 8); + 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, 8); + zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, zoomMaxExp); if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1); } } @@ -1274,12 +1278,12 @@ void UpdateDogfightCamera() { if (Input.GetKey(fmZoomInKey)) { - autoZoomMargin = Mathf.Clamp(autoZoomMargin + (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, 50); + 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, 50); + autoZoomMargin = Mathf.Clamp(autoZoomMargin - (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, autoZoomMarginMax); if (textInput) inputFields["autoZoomMargin"].currentValue = autoZoomMargin; } } @@ -1656,12 +1660,12 @@ void UpdateStationaryCamera() { if (Input.GetKey(fmZoomInKey)) { - zoomExp = Mathf.Clamp(zoomExp + (keyZoomSpeed * Time.fixedDeltaTime), 1, 8); + 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, 8); + zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, zoomMaxExp); if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1); } } @@ -1669,12 +1673,12 @@ void UpdateStationaryCamera() { if (Input.GetKey(fmZoomInKey)) { - autoZoomMargin = Mathf.Clamp(autoZoomMargin + (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, 50); + 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, 50); + autoZoomMargin = Mathf.Clamp(autoZoomMargin - (keyZoomSpeed * 10 * Time.fixedDeltaTime), 0, autoZoomMarginMax); if (textInput) inputFields["autoZoomMargin"].currentValue = autoZoomMargin; } } @@ -1886,12 +1890,12 @@ void UpdatePathingCam() //keyZoom Note: pathing doesn't use autoZoomMargin if (Input.GetKey(fmZoomInKey)) { - zoomExp = Mathf.Clamp(zoomExp + (keyZoomSpeed * Time.fixedDeltaTime), 1, 8); + 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, 8); + zoomExp = Mathf.Clamp(zoomExp - (keyZoomSpeed * Time.fixedDeltaTime), 1, zoomMaxExp); if (textInput) inputFields["zoomFactor"].currentValue = Mathf.Exp(zoomExp) / Mathf.Exp(1); } } @@ -2705,9 +2709,9 @@ void GuiWindow(int windowID) GUI.Label(LeftRect(++line), "Autozoom Margin: "); if (!textInput) { - autoZoomMargin = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + ((++line) * entryHeight), contentWidth - 45, entryHeight), autoZoomMargin, 0, 50); + 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("G3"), leftLabel); + GUI.Label(new Rect(leftIndent + contentWidth - 40, contentTop + ((line - 0.15f) * entryHeight), 40, entryHeight), autoZoomMargin.ToString("G4"), leftLabel); } else { @@ -2720,8 +2724,8 @@ void GuiWindow(int windowID) GUI.Label(LeftRect(++line), "Zoom:", leftLabel); if (!textInput) { - 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("G3") + "x", leftLabel); + 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); } else { @@ -2911,9 +2915,9 @@ void GuiWindow(int windowID) GUI.Label(SliderLabelLeft(++line, 55f), $"Distance:"); if (!textInput) { - dogfightDistance = GUI.HorizontalSlider(SliderRect(++line, 0f), dogfightDistance, 1f, 100f); + dogfightDistance = GUI.HorizontalSlider(SliderRect(++line, 0f, -8f), dogfightDistance, 1f, dogfightMaxDistance); if (!enableKeypad) dogfightDistance = MathUtils.RoundToUnit(dogfightDistance, 1f); - GUI.Label(SliderLabelRight(line), $"{dogfightDistance:G3}m"); + GUI.Label(SliderLabelRight(line, 8f), $"{dogfightDistance:G4}m"); } else { @@ -3757,6 +3761,7 @@ void Load() 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(); diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 89f23656..30ecedff 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,6 +1,7 @@ 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. v1.28.0 Improvements / Bugfixes: From a1c92a5f6063fe7545a71c368e0206803d843095 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 11 Aug 2023 12:02:51 +0200 Subject: [PATCH 174/263] Clean up setting/resetting audio source fields. --- CameraTools/CTPartAudioController.cs | 85 +++++++++++++++++++++------- CameraTools/CamTools.cs | 73 ++++++++++++++---------- 2 files changed, 106 insertions(+), 52 deletions(-) diff --git a/CameraTools/CTPartAudioController.cs b/CameraTools/CTPartAudioController.cs index 57cb2908..75c3aef9 100644 --- a/CameraTools/CTPartAudioController.cs +++ b/CameraTools/CTPartAudioController.cs @@ -8,15 +8,8 @@ public class CTPartAudioController : MonoBehaviour public AudioSource audioSource; - float origMinDist = 1; - float origMaxDist = 1; - - float modMinDist = 10; - float modMaxDist = 10000; - - AudioRolloffMode origRolloffMode; - - // Note: other fields are adjusted in CamTools.SetDoppler and CamTools.ResetDoppler + readonly float minDist = 10; + readonly float maxDist = 10000; void Awake() { @@ -32,11 +25,8 @@ void Start() return; } - origMinDist = audioSource.minDistance; - origMaxDist = audioSource.maxDistance; - origRolloffMode = audioSource.rolloffMode; - audioSource.rolloffMode = AudioRolloffMode.Logarithmic; - audioSource.spatialBlend = 1; + StoreOriginalSettings(); + ApplyEffects(); CamTools.OnResetCTools += OnResetCTools; } @@ -61,21 +51,21 @@ 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) { - 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() @@ -83,13 +73,64 @@ void OnDestroy() CamTools.OnResetCTools -= OnResetCTools; } + #region Store/Restore Original settings. + // 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; + 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; + } + + public void RestoreOriginalSettings() + { + if (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; + } + #endregion + + public void ApplyEffects() + { + if (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) { - audioSource.minDistance = origMinDist; - audioSource.maxDistance = origMaxDist; - audioSource.rolloffMode = origRolloffMode; + RestoreOriginalSettings(); } Destroy(this); } diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 6643f8cb..3ff964b8 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -19,9 +19,9 @@ public class CamTools : MonoBehaviour string Version = "unknown"; GameObject cameraParent; public Vessel vessel; - List engines = new List(); - List cockpits = new List(); - public static HashSet ignoreVesselTypesForAudio = new HashSet { VesselType.Debris, VesselType.SpaceObject, VesselType.Unknown, VesselType.Flag }; // Ignore some vessel types to avoid using up all the SoundManager's channels. + List engines = new(); + List cockpits = new(); + public static HashSet ignoreVesselTypesForAudio = new() { 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; @@ -112,7 +112,7 @@ enum fmModeTypes { Position, Speed }; string guiOffsetRight = "50"; string guiOffsetUp = "5"; [CTPersistantField] public bool targetCoM = false; - static List> debugMessages = new List>(); + 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); @@ -126,7 +126,7 @@ enum fmModeTypes { Position, Speed }; GUIStyle inputFieldStyle; GUIStyle watermarkStyle; Dictionary inputFields; - List> debug2Messages = new List>(); + List> debug2Messages = new(); void Debug2Log(string m) => debug2Messages.Add(new Tuple(Time.time, m)); float lastSavedTime = 0; @@ -160,11 +160,11 @@ enum fmModeTypes { Position, Speed }; #region Audio Fields AudioSource[] audioSources; - (float dopplerLevel, AudioVelocityUpdateMode velocityUpdateMode, bool bypassEffects, float spatialBlend, bool spatialize)[] originalAudioSourceDoppler; - HashSet excludeAudioSources = new HashSet { "MusicLogic", "windAS", "windHowlAS", "windTearAS", "sonicBoomAS" }; // Don't adjust music or atmospheric audio. + 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; [CTPersistantField] public bool useAudioEffects = true; - public static double speedOfSound = 330; + public static double speedOfSound = 340; #endregion #region Camera Shake @@ -1543,7 +1543,7 @@ void UpdateStationaryCamera() if (useAudioEffects) { speedOfSound = 233 * MathUtils.Sqrt(1 + (FlightGlobals.getExternalTemperature(vessel.GetWorldPos3D(), vessel.mainBody) / 273.15)); - //Debug.Log("[CameraTools]: speed of sound: " + speedOfSound); + // 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. @@ -2280,18 +2280,16 @@ void SetDoppler(bool includeActiveVessel) } // 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. + // 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, AudioVelocityUpdateMode, bool, float, bool)[audioSources.Length]; - // Debug.Log($"DEBUG AudioSource pitch: "+ string.Join(", ", audioSources.Where(a => a.isPlaying).Select(a => $"{a.name}: {a.pitch}"))); + // Debug.Log("CameraTools.DEBUG audioSources: " + string.Join(", ", audioSources.Select(a => a.name))); + originalAudioSourceSettings.Clear(); for (int i = 0; i < audioSources.Length; i++) { - // Debug.Log("CameraTools.DEBUG audioSources: " + string.Join(", ", audioSources.Select(a => a.name))); if (excludeAudioSources.Contains(audioSources[i].name)) continue; - originalAudioSourceDoppler[i] = (audioSources[i].dopplerLevel, audioSources[i].velocityUpdateMode, audioSources[i].bypassEffects, audioSources[i].spatialBlend, audioSources[i].spatialize); if (!includeActiveVessel) { @@ -2299,19 +2297,27 @@ void SetDoppler(bool includeActiveVessel) if (p && p.vessel.isActiveVessel) continue; } - audioSources[i].dopplerLevel = 1; - audioSources[i].velocityUpdateMode = AudioVelocityUpdateMode.Fixed; - audioSources[i].bypassEffects = false; - audioSources[i].spatialBlend = 1; - audioSources[i].spatialize = true; - var part = audioSources[i].gameObject.GetComponentInParent(); - if (part != null && part.vessel != null && !ignoreVesselTypesForAudio.Contains(part.vessel.vesselType)) + if (part != null) { - CTPartAudioController pa = audioSources[i].gameObject.GetComponent(); - if (pa == null) pa = audioSources[i].gameObject.AddComponent(); - pa.audioSource = audioSources[i]; - // if (DEBUG && audioSources[i].isPlaying) Debug.Log($"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}"); + if (part.vessel != null && !ignoreVesselTypesForAudio.Contains(part.vessel.vesselType)) + { + CTPartAudioController pa = audioSources[i].gameObject.GetComponent(); + if (pa == null) pa = audioSources[i].gameObject.AddComponent(); + pa.audioSource = audioSources[i]; + pa.StoreOriginalSettings(); + pa.ApplyEffects(); + // if (DEBUG && audioSources[i].isPlaying) Debug.Log($"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. + { + 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; } } @@ -2325,14 +2331,21 @@ void ResetDoppler() 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; - audioSources[i].dopplerLevel = originalAudioSourceDoppler[i].dopplerLevel; - audioSources[i].velocityUpdateMode = originalAudioSourceDoppler[i].velocityUpdateMode; - audioSources[i].bypassEffects = originalAudioSourceDoppler[i].bypassEffects; - audioSources[i].spatialBlend = originalAudioSourceDoppler[i].spatialBlend; - audioSources[i].spatialize = originalAudioSourceDoppler[i].spatialize; + CTPartAudioController pa = audioSources[i].gameObject.GetComponent(); + if (pa == null) continue; + pa.RestoreOriginalSettings(); } hasSetDoppler = false; From 0ae377dd440d983870d414dc2ebe07d002efd7c1 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 11 Aug 2023 12:29:51 +0200 Subject: [PATCH 175/263] Apply some Roslyn suggested clean-up. --- CameraTools/CamTools.cs | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 3ff964b8..c5cd1294 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -395,32 +395,23 @@ void Start() 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); - cStyle.fontStyle = UnityEngine.FontStyle.Bold; - cStyle.fontSize = 18; - cStyle.alignment = TextAnchor.UpperLeft; + 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(); - centerLabel.alignment = TextAnchor.UpperCenter; + centerLabel = new GUIStyle { alignment = TextAnchor.UpperCenter }; centerLabel.normal.textColor = Color.white; - leftLabel = new GUIStyle(); - leftLabel.alignment = TextAnchor.UpperLeft; + leftLabel = new GUIStyle { alignment = TextAnchor.UpperLeft }; leftLabel.normal.textColor = Color.white; - rightLabel = new GUIStyle(leftLabel); - rightLabel.alignment = TextAnchor.UpperRight; - leftLabelBold = new GUIStyle(leftLabel); - leftLabelBold.fontStyle = FontStyle.Bold; - titleStyle = new GUIStyle(centerLabel); - titleStyle.fontSize = 24; - titleStyle.alignment = TextAnchor.MiddleCenter; + 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); + contentWidth = windowWidth - 2 * leftIndent; inputFields = new Dictionary { {"autoZoomMargin", gameObject.AddComponent().Initialise(0, autoZoomMargin, 0f, autoZoomMarginMax, 4)}, From 7d5cf77b0fb2b407aef8867c70477fd1dfc4a5f8 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 11 Aug 2023 12:47:18 +0200 Subject: [PATCH 176/263] Perform a check for a spatializer and disable doppler effects if none is found. --- CameraTools/CamTools.cs | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index c5cd1294..f9a72be5 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -163,6 +163,7 @@ enum fmModeTypes { Position, Speed }; 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 bool useAudioEffects = true; public static double speedOfSound = 340; #endregion @@ -381,6 +382,8 @@ void Start() bdArmory = BDArmory.instance; betterTimeWarp = BetterTimeWarp.instance; timeControl = TimeControl.instance; + hasSpatializerPlugin = !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) { @@ -2240,10 +2243,7 @@ float GetTotalThrust() #region Atmospherics void AddAtmoAudioControllers(bool includeActiveVessel) { - if (!useAudioEffects) - { - return; - } + if (!useAudioEffects) return; foreach (var vessel in FlightGlobals.Vessels) { @@ -2260,15 +2260,7 @@ void AddAtmoAudioControllers(bool includeActiveVessel) void SetDoppler(bool includeActiveVessel) { - if (hasSetDoppler) - { - return; - } - - if (!useAudioEffects) - { - return; - } + if (hasSetDoppler || !useAudioEffects || !hasSpatializerPlugin) 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. @@ -2317,10 +2309,7 @@ void SetDoppler(bool includeActiveVessel) void ResetDoppler() { - if (!hasSetDoppler) - { - return; - } + if (!hasSetDoppler) return; foreach (var (index, dopplerLevel, velocityUpdateMode, bypassEffects, spatialize, spatialBlend) in originalAudioSourceSettings) // Set/reset part audio separately from others. { From e8e4ef2370229348564d89359b280215aef9ce85 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 11 Aug 2023 12:49:10 +0200 Subject: [PATCH 177/263] Update changelog --- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 30ecedff..c6fc51b1 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -2,6 +2,7 @@ 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. v1.28.0 Improvements / Bugfixes: From 67c0da8512ee72787234068c7312537d74740374 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 11 Aug 2023 12:57:23 +0200 Subject: [PATCH 178/263] Do nothing with part audio until the original settings are stored. --- CameraTools/CTPartAudioController.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CameraTools/CTPartAudioController.cs b/CameraTools/CTPartAudioController.cs index 75c3aef9..18abe775 100644 --- a/CameraTools/CTPartAudioController.cs +++ b/CameraTools/CTPartAudioController.cs @@ -25,8 +25,6 @@ void Start() return; } - StoreOriginalSettings(); - ApplyEffects(); CamTools.OnResetCTools += OnResetCTools; } @@ -44,6 +42,7 @@ void FixedUpdate() 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); @@ -74,6 +73,7 @@ void OnDestroy() } #region Store/Restore Original settings. + bool origSettingsStored = false; // Any settings that get adjusted in ApplyEffects should be added here. float origMinDist; float origMaxDist; @@ -88,6 +88,7 @@ void OnDestroy() public void StoreOriginalSettings() { if (audioSource == null) return; + origSettingsStored = true; origMinDist = audioSource.minDistance; origMaxDist = audioSource.maxDistance; origBypassEffects = audioSource.bypassEffects; @@ -101,7 +102,7 @@ public void StoreOriginalSettings() public void RestoreOriginalSettings() { - if (audioSource == null) return; + if (!origSettingsStored || audioSource == null) return; audioSource.minDistance = origMinDist; audioSource.maxDistance = origMaxDist; audioSource.bypassEffects = origBypassEffects; From 6f529425538b0ef216c9b8dd9abf417696a2788a Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 11 Aug 2023 12:58:27 +0200 Subject: [PATCH 179/263] Also in ApplyEffects --- CameraTools/CTPartAudioController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CameraTools/CTPartAudioController.cs b/CameraTools/CTPartAudioController.cs index 18abe775..2001316d 100644 --- a/CameraTools/CTPartAudioController.cs +++ b/CameraTools/CTPartAudioController.cs @@ -117,7 +117,7 @@ public void RestoreOriginalSettings() public void ApplyEffects() { - if (audioSource == null) return; + if (!origSettingsStored || audioSource == null) return; audioSource.bypassEffects = false; audioSource.spatialize = true; audioSource.spatialBlend = 1; From 5f6109fc95a8b485a77509d46892baa76babd1fd Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 11 Aug 2023 18:09:28 +0200 Subject: [PATCH 180/263] Rework the sonic boom calculations, allowing them to reset and removing the booming when switching vessels/restarting camera modes. --- CameraTools/CTAtmosphericAudioController.cs | 69 ++++++++++++------- CameraTools/CamTools.cs | 10 +-- .../GameData/CameraTools/Changelog.txt | 1 + 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/CameraTools/CTAtmosphericAudioController.cs b/CameraTools/CTAtmosphericAudioController.cs index 4757785c..ef468102 100644 --- a/CameraTools/CTAtmosphericAudioController.cs +++ b/CameraTools/CTAtmosphericAudioController.cs @@ -1,28 +1,29 @@ using UnityEngine; +using System.Collections; using System.Collections.Generic; namespace CameraTools { public class CTAtmosphericAudioController : MonoBehaviour { - static Dictionary audioClips; + 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() { - if (audioClips is null) audioClips = new Dictionary(); vessel = GetComponent(); windAudioSource = new GameObject("windAS").AddComponent(); @@ -86,21 +87,30 @@ void Awake() sonicBoomSource.Stop(); sonicBoomSource.spatialBlend = 1; sonicBoomSource.transform.parent = vessel.transform; + delayedSonicBoomSource = Instantiate(sonicBoomSource); - 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) - { - playedBoom = true; - } - + 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.isPlaying) windAudioSource.Stop(); + if (windHowlAudioSource.isPlaying) windHowlAudioSource.Stop(); + if (windTearAudioSource.isPlaying) windTearAudioSource.Stop(); + if (sonicBoomSource.isPlaying) sonicBoomSource.Stop(); + if (delayedSonicBoomSource.isPlaying) delayedSonicBoomSource.Stop(); + } + void FixedUpdate() { if (vessel == null || !vessel.loaded || !vessel.isActiveAndEnabled) return; // Vessel is dead or not ready. - if (FlightCamera.fetch == null || FlightCamera.fetch.mainCamera == null) return; // Flight camera is broken. + 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; @@ -108,37 +118,45 @@ void FixedUpdate() { 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)); + 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) { - 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) { - 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; } @@ -215,6 +233,7 @@ void OnDestroy() void OnResetCTools() { + Reset(); // Prevent booming when switching vessels/restarting camera modes. Destroy(this); } diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index f9a72be5..7d549c4e 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -15,6 +15,7 @@ public class CamTools : MonoBehaviour { #region Fields public static CamTools fetch; + public static FlightCamera flightCamera; string Version = "unknown"; GameObject cameraParent; @@ -31,7 +32,6 @@ public class CamTools : MonoBehaviour float origDistance; FlightCamera.Modes origMode; float origFov = 60; - FlightCamera flightCamera; Part camTarget = null; Vector3 cameraUp = Vector3.up; public bool cameraToolActive = false; @@ -164,6 +164,7 @@ enum fmModeTypes { Position, Speed }; 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; public static double speedOfSound = 340; #endregion @@ -382,7 +383,7 @@ void Start() bdArmory = BDArmory.instance; betterTimeWarp = BetterTimeWarp.instance; timeControl = TimeControl.instance; - hasSpatializerPlugin = !string.IsNullOrEmpty(AudioSettings.GetSpatializerPluginName()); // Check for a spatializer plugin, otherwise doppler effects won't work. + 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) @@ -1386,7 +1387,7 @@ void StartStationaryCamera() 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) ? true : false; + hasTarget = camTarget != null; lastVesselCoM = vessel.CoM; // Camera position. @@ -2253,8 +2254,7 @@ void AddAtmoAudioControllers(bool includeActiveVessel) } if (ignoreVesselTypesForAudio.Contains(vessel.vesselType)) continue; - if (vessel.gameObject.GetComponent() == null) - { vessel.gameObject.AddComponent(); } + vessel.gameObject.AddComponent(); // Always add, since they get removed with OnResetCTools triggers. } } diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index c6fc51b1..7eb8cabd 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -3,6 +3,7 @@ Improvements / Bugfixes: - 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: From cde9d01eb09d5f8d683954cc4900391ca73e651a Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 11 Aug 2023 23:35:36 +0200 Subject: [PATCH 181/263] Add some more optimisation and a missing volume factor for wind tear. --- CameraTools/CTAtmosphericAudioController.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/CameraTools/CTAtmosphericAudioController.cs b/CameraTools/CTAtmosphericAudioController.cs index ef468102..7f96d7e3 100644 --- a/CameraTools/CTAtmosphericAudioController.cs +++ b/CameraTools/CTAtmosphericAudioController.cs @@ -120,6 +120,7 @@ void FixedUpdate() srfSpeed = Mathf.Min(srfSpeed, 550f); float angleToCam = Mathf.Clamp(Vector3.Angle(vessel.srf_velocity, CamTools.flightCamera.transform.position - vessel.transform.position), 1, 180); + // 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; @@ -163,6 +164,8 @@ void FixedUpdate() lagAudioFactor *= waveFrontFactor; float sqrAccel = (float)vessel.acceleration.sqrMagnitude; + float vesselMass = vessel.GetTotalMass(); + float dynamicPressurekPa = (float)vessel.dynamicPressurekPa; //windloop if (!windAudioSource.isPlaying) @@ -171,20 +174,19 @@ void FixedUpdate() // 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) { 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); @@ -194,10 +196,10 @@ void FixedUpdate() 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 * lagAudioFactor; windTearAudioSource.minDistance = lagAudioFactor * 1; windTearAudioSource.maxDistance = Mathf.Clamp(lagAudioFactor * 2500, windTearAudioSource.minDistance, 16000); From d80af125600a622961a30fd06335a93792db4433 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 11 Aug 2023 23:36:50 +0200 Subject: [PATCH 182/263] Bump version numbers for release. --- .../Distribution/GameData/CameraTools/CameraTools.version | 2 +- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 1 + CameraTools/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index ec684b00..e481c728 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -5,7 +5,7 @@ "CHANGE_LOG_URL": "https://github.com/BrettRyland/CameraTools/blob/master/CameraTools/Distribution/GameData/CameraTools/Changelog.txt", "VERSION": { "MAJOR": 1, - "MINOR": 28, + "MINOR": 29, "PATCH": 0, "BUILD": 0 }, diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 7eb8cabd..7a97cf35 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,4 @@ +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.). diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index e2ac7768..0231fd74 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.28.0.0" )] -[assembly: AssemblyFileVersion( "1.28.0" )] +[assembly: AssemblyVersion( "1.29.0.0" )] +[assembly: AssemblyFileVersion( "1.29.0" )] From 2e963ea15813ec8f499fdada6529a347316a7ff2 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 11 Aug 2023 23:47:01 +0200 Subject: [PATCH 183/263] Also always add part audio controllers since they get removed when OnResetCTools triggers. --- CameraTools/CamTools.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 7d549c4e..6ad2dac4 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -2254,7 +2254,7 @@ void AddAtmoAudioControllers(bool includeActiveVessel) } if (ignoreVesselTypesForAudio.Contains(vessel.vesselType)) continue; - vessel.gameObject.AddComponent(); // Always add, since they get removed with OnResetCTools triggers. + vessel.gameObject.AddComponent(); // Always add, since they get removed when OnResetCTools triggers. } } @@ -2285,8 +2285,7 @@ void SetDoppler(bool includeActiveVessel) { if (part.vessel != null && !ignoreVesselTypesForAudio.Contains(part.vessel.vesselType)) { - CTPartAudioController pa = audioSources[i].gameObject.GetComponent(); - if (pa == null) pa = audioSources[i].gameObject.AddComponent(); + var pa = audioSources[i].gameObject.AddComponent(); // Always add, since they get removed when OnResetCTools triggers. pa.audioSource = audioSources[i]; pa.StoreOriginalSettings(); pa.ApplyEffects(); From 2f6dc7394a5026bff9ca0a1e962a6f40b343fc68 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sat, 12 Aug 2023 00:21:41 +0200 Subject: [PATCH 184/263] Fix wind-tear audio being overly loud from previous tweaks. --- CameraTools/CTAtmosphericAudioController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CameraTools/CTAtmosphericAudioController.cs b/CameraTools/CTAtmosphericAudioController.cs index 7f96d7e3..5694312d 100644 --- a/CameraTools/CTAtmosphericAudioController.cs +++ b/CameraTools/CTAtmosphericAudioController.cs @@ -199,7 +199,7 @@ void FixedUpdate() 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 * lagAudioFactor; + windTearAudioSource.volume = pressureFactor3 * massFactor3 * Mathf.Clamp01(lagAudioFactor); windTearAudioSource.minDistance = lagAudioFactor * 1; windTearAudioSource.maxDistance = Mathf.Clamp(lagAudioFactor * 2500, windTearAudioSource.minDistance, 16000); From 05f18ff7af1074b6dc8780fd44e7a31e78dd7cfc Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sat, 12 Aug 2023 00:24:26 +0200 Subject: [PATCH 185/263] Fix wind-tear audio being overly loud from recent tweaks. --- .../Distribution/GameData/CameraTools/CameraTools.version | 2 +- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 4 ++++ CameraTools/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index e481c728..31d05eab 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -6,7 +6,7 @@ "VERSION": { "MAJOR": 1, "MINOR": 29, - "PATCH": 0, + "PATCH": 1, "BUILD": 0 }, "KSP_VERSION": { diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 7a97cf35..4e5d90cd 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,7 @@ +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). diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index 0231fd74..4b381ac5 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.29.0.0" )] -[assembly: AssemblyFileVersion( "1.29.0" )] +[assembly: AssemblyVersion( "1.29.1.0" )] +[assembly: AssemblyFileVersion( "1.29.1" )] From e4d36726d085eb8f7814935d485aa94e4e594e36 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 2 Nov 2023 23:12:07 +0100 Subject: [PATCH 186/263] Fix an AudioSource memory leak. --- CameraTools/CTAtmosphericAudioController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CameraTools/CTAtmosphericAudioController.cs b/CameraTools/CTAtmosphericAudioController.cs index 5694312d..2f1db5a7 100644 --- a/CameraTools/CTAtmosphericAudioController.cs +++ b/CameraTools/CTAtmosphericAudioController.cs @@ -1,5 +1,4 @@ using UnityEngine; -using System.Collections; using System.Collections.Generic; namespace CameraTools @@ -227,6 +226,7 @@ void FixedUpdate() void OnDestroy() { 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); From 57686455f5fdf530ebb375bbae551edb2db60fc0 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 2 Nov 2023 23:13:37 +0100 Subject: [PATCH 187/263] Add changelog entry. Bump version number for bugfix release. --- .../Distribution/GameData/CameraTools/CameraTools.version | 2 +- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 4 ++++ CameraTools/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index 31d05eab..87cda204 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -6,7 +6,7 @@ "VERSION": { "MAJOR": 1, "MINOR": 29, - "PATCH": 1, + "PATCH": 2, "BUILD": 0 }, "KSP_VERSION": { diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 4e5d90cd..05eab1b7 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,7 @@ +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. diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index 4b381ac5..70bc4b1d 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.29.1.0" )] -[assembly: AssemblyFileVersion( "1.29.1" )] +[assembly: AssemblyVersion( "1.29.2.0" )] +[assembly: AssemblyFileVersion( "1.29.2" )] From 0d14150944ec25b881e47b83eaf2b1f9b989a812 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 12 Nov 2023 02:11:06 +0100 Subject: [PATCH 188/263] Fix aero FX not being applied and provide a toggle to disable them. Clean up DEBUG2 messages. --- CameraTools/CamTools.cs | 130 ++++++++++-------- .../GameData/CameraTools/Changelog.txt | 3 + 2 files changed, 74 insertions(+), 59 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 6ad2dac4..505984d8 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -28,6 +28,7 @@ public class CamTools : MonoBehaviour Vector3 origLocalPosition; Quaternion origLocalRotation; Transform origParent; + Vector3 origParentPosition; float origNearClip; float origDistance; FlightCamera.Modes origMode; @@ -126,7 +127,7 @@ enum fmModeTypes { Position, Speed }; GUIStyle inputFieldStyle; GUIStyle watermarkStyle; Dictionary inputFields; - List> debug2Messages = new(); + readonly List> debug2Messages = new(); void Debug2Log(string m) => debug2Messages.Add(new Tuple(Time.time, m)); float lastSavedTime = 0; @@ -166,6 +167,7 @@ enum fmModeTypes { Position, Speed }; 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 @@ -538,26 +540,26 @@ void KrakensbaneWarpCorrection() else if (!inHighWarp && useObtVel != wasUsingObtVel) // Only needed when crossing the boundary. floatingKrakenAdjustment += ((useObtVel ? vessel.obt_velocity : vessel.srf_velocity) - Krakensbane.GetFrameVelocity()) * TimeWarp.fixedDeltaTime; cameraParent.transform.position += 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")); - } + // 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; } case ToolModes.StationaryCamera: @@ -760,6 +762,7 @@ void Update() default: break; } + if (enableVFX) origParent.position = cameraParent.transform.position; // KSP's aero FX are only enabled when close to the origParent's position. } } @@ -769,6 +772,7 @@ void FixedUpdate() 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 (DEBUG2 && !GameIsPaused) debug2Messages.Clear(); if (cameraToolActive) @@ -1053,7 +1057,7 @@ void UpdateDogfightCamera() dogfightLastTarget = true; dogfightLastTargetVelocity = Vector3.zero; dogfightLastTargetPosition = bdArmory.GetCentroid(); - if (DEBUG2 && !GameIsPaused) Debug2Log($"Centroid: {dogfightLastTargetPosition:G3}"); + // if (DEBUG2 && !GameIsPaused) Debug2Log($"Centroid: {dogfightLastTargetPosition:G3}"); } else if (dogfightTarget) { @@ -1120,15 +1124,15 @@ void UpdateDogfightCamera() } if (DEBUG2 && !GameIsPaused) { - 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}"); + // 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}"); } //rotation @@ -1340,9 +1344,10 @@ void UpdateDogfightCamera() { var cameraRadarAltitude = GetRadarAltitudeAtPos(cameraTransform.position); if (cameraRadarAltitude < 2f && (vessel.Landed || cameraRadarAltitude > -dogfightDistance)) cameraTransform.position += (2f - cameraRadarAltitude) * cameraUp; // Prevent viewing from under the surface if near the surface. - if (DEBUG2 && !GameIsPaused) Debug2Log($"camera altitude: {GetRadarAltitudeAtPos(cameraTransform.position):G3} ({cameraRadarAltitude:G3})"); + + // if (DEBUG2 && !GameIsPaused) Debug2Log($"camera altitude: {GetRadarAltitudeAtPos(cameraTransform.position):G3} ({cameraRadarAltitude:G3})"); } - else if (DEBUG2 && !GameIsPaused) Debug2Log($"vessel not landed"); + // else if (DEBUG2 && !GameIsPaused) Debug2Log($"vessel not landed"); } if (dogfightTarget != dogfightPrevTarget) @@ -1591,32 +1596,32 @@ void UpdateStationaryCamera() flightCamera.transform.rotation = Quaternion.LookRotation(lastTargetPosition - flightCamera.transform.position, upAxis); } - if (DEBUG2 && !GameIsPaused) - { - var Δ = lastOffset - (vessel.transform.position - flightCamera.transform.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: " + flightCamera.transform.position.ToString("G6")); - Debug2Log("ΔCamera: " + (flightCamera.transform.position - lastCameraPosition).ToString("G6")); - Debug2Log("δp: " + (cameraParent.transform.position - lastCamParentPosition).ToString("G6")); - Debug2Log("ΔCamera + δp: " + (flightCamera.transform.position - lastCameraPosition + cameraParent.transform.position - lastCamParentPosition).ToString("G6")); - Debug2Log("δ: " + lastOffsetSinceLastFrame.ToString("G6")); - Debug2Log("Δ: " + Δ.ToString("G6")); - Debug2Log("δ + Δ: " + (lastOffsetSinceLastFrame + Δ).ToString("G6")); - lastOffset = vessel.transform.position - flightCamera.transform.position; - lastCamParentPosition = cameraParent.transform.position; - } + // if (DEBUG2 && !GameIsPaused) + // { + // var Δ = lastOffset - (vessel.transform.position - flightCamera.transform.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: " + flightCamera.transform.position.ToString("G6")); + // Debug2Log("ΔCamera: " + (flightCamera.transform.position - lastCameraPosition).ToString("G6")); + // Debug2Log("δp: " + (cameraParent.transform.position - lastCamParentPosition).ToString("G6")); + // Debug2Log("ΔCamera + δp: " + (flightCamera.transform.position - lastCameraPosition + cameraParent.transform.position - lastCamParentPosition).ToString("G6")); + // Debug2Log("δ: " + lastOffsetSinceLastFrame.ToString("G6")); + // Debug2Log("Δ: " + Δ.ToString("G6")); + // Debug2Log("δ + Δ: " + (lastOffsetSinceLastFrame + Δ).ToString("G6")); + // lastOffset = vessel.transform.position - flightCamera.transform.position; + // lastCamParentPosition = cameraParent.transform.position; + // } //free move if (enableKeypad && !boundThisFrame) @@ -2458,6 +2463,7 @@ public void RevertCamera() flightCamera.transform.parent = origParent; if (origParent != null) // Restore the camera to the original local offsets from the original gameObject. { + origParent.position = origParentPosition; flightCamera.transform.localPosition = origLocalPosition; flightCamera.transform.localRotation = origLocalRotation; flightCamera.SetDistanceImmediate(origDistance); @@ -2506,6 +2512,7 @@ void SaveOriginalCamera() origLocalPosition = flightCamera.transform.localPosition; origLocalRotation = flightCamera.transform.localRotation; origParent = flightCamera.transform.parent; + origParentPosition = origParent.position; origNearClip = HighLogic.LoadedSceneIsFlight ? flightCamera.mainCamera.nearClipPlane : Camera.main.nearClipPlane; origDistance = flightCamera.Distance; origMode = flightCamera.mode; @@ -2693,6 +2700,10 @@ void GuiWindow(int windowID) line++; useAudioEffects = GUI.Toggle(LabelRect(++line), useAudioEffects, "Use Audio Effects"); + if (enableVFX != (enableVFX = GUI.Toggle(LabelRect(++line), enableVFX, "Enable Visual Effects"))) + { + 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, "Auto-Enable for BDArmory"); line++; @@ -3635,6 +3646,7 @@ void SetCameraParent(Transform referenceTransform, bool resetToCoM = false) flightCamera.transform.localPosition = cameraParent.transform.InverseTransformPoint(referenceTransform.position); flightCamera.transform.localRotation = Quaternion.identity; } + origParent.position = enableVFX ? cameraParent.transform.position : FlightGlobals.currentMainBody.position; } void SetDeathCam() diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 05eab1b7..e9318c99 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,6 @@ +Improvements / Bugfixes: +- Fix aero FX not being applied and provide a toggle to disable them. + v1.29.2 Improvements / Bugfixes: - Fix an AudioSource memory leak. From a71eaf15d616f66c877b3a91b9d05ba5c6c852b8 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 12 Nov 2023 22:33:02 +0100 Subject: [PATCH 189/263] Add check for BDOrbitalAI. --- CameraTools/ModIntegration/BDArmory.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CameraTools/ModIntegration/BDArmory.cs b/CameraTools/ModIntegration/BDArmory.cs index 7791c682..ff0b18cc 100644 --- a/CameraTools/ModIntegration/BDArmory.cs +++ b/CameraTools/ModIntegration/BDArmory.cs @@ -227,6 +227,13 @@ public void CheckForBDAI(Vessel v) aiComponent = (object)p.GetComponent("BDModuleSurfaceAI"); return; } + if (p.GetComponent("BDModuleOrbitalAI")) + { + hasBDAI = true; + hasPilotAI = true; + aiComponent = (object)p.GetComponent("BDModuleOrbitalAI"); + return; + } } } } From 0fff736f3c796f684381a9a6605c89a6e9917dc1 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 14 Nov 2023 00:51:19 +0100 Subject: [PATCH 190/263] Adjustments to fix some issues when BDArmory should be inhibiting CameraTools and more detailed debugging messages. --- CameraTools/CamTools.cs | 47 ++++++++++++------- .../GameData/CameraTools/Changelog.txt | 1 + CameraTools/ModIntegration/BDArmory.cs | 45 ++++++++++++------ 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 505984d8..924ba376 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -746,6 +746,7 @@ void Update() waitingForPosition = false; } + if (BDArmory.IsInhibited) return; // Don't do anything else while BDA is inhibiting us. if (cameraToolActive) { switch (toolMode) @@ -772,6 +773,7 @@ void FixedUpdate() 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(); @@ -788,7 +790,7 @@ void FixedUpdate() } else { - message = "Someone has stolen the camera parent! Abort!"; + message = $"Someone has stolen the camera parent ({flightCamera.transform.parent.name} vs {(hasDied ? deathCam.transform.name : cameraParent.transform.name)})! Abort!"; Debug.Log("[CameraTools]: " + message); if (DEBUG) DebugLog(message); cameraToolActive = false; @@ -882,6 +884,7 @@ 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) { deathCam.transform.position += deathCamVelocity * TimeWarp.deltaTime; @@ -955,12 +958,18 @@ public void cameraActivate() void StartDogfightCamera() { toolMode = ToolModes.DogfightCamera; - if (FlightGlobals.ActiveVessel == null) + vessel = FlightGlobals.ActiveVessel; + if (vessel == null) { Debug.Log("[CameraTools]: No active vessel."); return; } - if (DEBUG) { Debug.Log("[CameraTools]: Starting dogfight camera."); DebugLog("Starting dogfight camera"); } + if (DEBUG) + { + message = $"Starting dogfight camera for {vessel.vesselName}{(dogfightTarget ? $" vs {dogfightTarget.vesselName}" : "")}."; + Debug.Log($"[CameraTools]: {message}"); + DebugLog(message); + } if (MouseAimFlight.IsMouseAimActive()) { @@ -968,7 +977,7 @@ void StartDogfightCamera() dogfightLastTarget = true; dogfightVelocityChase = false; } - else if (bdArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 1) + else if (BDArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 1) { dogfightLastTarget = true; dogfightVelocityChase = false; @@ -992,7 +1001,6 @@ void StartDogfightCamera() dogfightPrevTarget = dogfightTarget; hasDied = false; - vessel = FlightGlobals.ActiveVessel; cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.CoM).normalized; if (flightCamera.transform.parent != cameraParent.transform) @@ -1052,7 +1060,7 @@ void UpdateDogfightCamera() mouseAimFlightTargetLocal = cameraTransform.InverseTransformDirection(mouseAimFlightTarget); dogfightLastTargetPosition = (mouseAimFlightTarget.normalized + vessel.srf_vel_direction) * 5000f + vessel.CoM; } - else if (bdArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 1) + else if (BDArmory.hasBDA && bdArmory.useCentroid && bdArmory.bdWMVessels.Count > 1) { dogfightLastTarget = true; dogfightLastTargetVelocity = Vector3.zero; @@ -1337,7 +1345,7 @@ void UpdateDogfightCamera() } } - if (bdArmory.hasBDA && bdArmory.hasBDAI && (bdArmory.useBDAutoTarget || (bdArmory.useCentroid && bdArmory.bdWMVessels.Count < 2))) + if (BDArmory.hasBDA && bdArmory.hasBDAI && (bdArmory.useBDAutoTarget || (bdArmory.useCentroid && bdArmory.bdWMVessels.Count < 2))) { bdArmory.UpdateAIDogfightTarget(); // Using delegates instead of reflection allows us to check every frame. if (vessel.LandedOrSplashed) @@ -2360,7 +2368,7 @@ void SwitchToVessel(Vessel v) cockpits.Clear(); engines.Clear(); - if (bdArmory.hasBDA) + if (BDArmory.hasBDA) { bdArmory.CheckForBDAI(v); bdArmory.CheckForBDWM(v); @@ -2369,7 +2377,8 @@ void SwitchToVessel(Vessel v) } if (cameraToolActive) { - if (randomMode) + if (BDArmory.IsInhibited) RevertCamera(); + else if (randomMode) { var lowAlt = Math.Max(30d, -5d * vessel.verticalSpeed * Mathf.Max(0, Vector3.Dot(vessel.srf_vel_direction, -cameraUp))); // 30m or up to 5s to impact (depending on angle), whichever is higher. var stationarySurfaceVessel = (vessel.Landed && vessel.Speed() < 1) || (vessel.Splashed && vessel.Speed() < 5); // Land or water vessel that isn't moving much. @@ -2377,7 +2386,7 @@ void SwitchToVessel(Vessel v) { StartStationaryCamera(); } - else if (bdArmory.hasBDA && bdArmory.isBDMissile) + else if (BDArmory.hasBDA && bdArmory.isBDMissile) { dogfightTarget = null; StartDogfightCamera(); // Use dogfight chase mode for BDA missiles. @@ -2460,6 +2469,7 @@ public void RevertCamera() } 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. { @@ -2699,12 +2709,12 @@ void GuiWindow(int windowID) } line++; - useAudioEffects = GUI.Toggle(LabelRect(++line), useAudioEffects, "Use Audio Effects"); + useAudioEffects = GUI.Toggle(LabelRect(++line), useAudioEffects, "Enable Audio Effects"); if (enableVFX != (enableVFX = GUI.Toggle(LabelRect(++line), enableVFX, "Enable Visual Effects"))) { 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, "Auto-Enable for BDArmory"); + if (BDArmory.hasBDA) bdArmory.autoEnableForBDA = GUI.Toggle(LabelRect(++line), bdArmory.autoEnableForBDA, "Auto-Enable for BDArmory"); line++; if (autoFOV && toolMode != ToolModes.Pathing) @@ -2858,7 +2868,7 @@ void GuiWindow(int windowID) { tVesselLabel = "MouseAimFlight"; } else if (showingVesselList) { tVesselLabel = "Clear"; } - else if (bdArmory.hasBDA && bdArmory.useCentroid) + else if (BDArmory.hasBDA && bdArmory.useCentroid) { tVesselLabel = "Centroid"; } else if (dogfightTarget) { tVesselLabel = dogfightTarget.vesselName; } @@ -2890,7 +2900,7 @@ void GuiWindow(int windowID) } } } - if (bdArmory.hasBDA) + if (BDArmory.hasBDA) { if (bdArmory.hasBDAI) { @@ -3540,14 +3550,14 @@ void CurrentVesselWillDestroy(Vessel v) hasDied = true; diedTime = Time.time; + // Something borks the camera position/rotation when the target/parent is set to none/null. This fixes that. + deathCamVelocity = (vessel.radarAltitude > 10d ? vessel.Velocity() : Vector3d.zero) / 2f; // Track the explosion a bit. if (DEBUG) { - message = "Activating death camera."; + message = $"Activating death camera with speed {deathCamVelocity.magnitude:G3}m/s."; Debug.Log("[CameraTools]: " + message); DebugLog(message); } - // Something borks the camera position/rotation when the target/parent is set to none/null. This fixes that. - deathCamVelocity = (vessel.radarAltitude > 10d ? vessel.Velocity() : Vector3d.zero) / 2f; // Track the explosion a bit. SetDeathCam(); } } @@ -3633,7 +3643,7 @@ public void SetZoomImmediate(float zoom) void SetCameraParent(Transform referenceTransform, bool resetToCoM = false) { - if (DEBUG) Debug.Log($"[CameraTools]: Setting the camera parent to {referenceTransform.gameObject.name} from {flightCamera.gameObject.name}, reset-to-CoM: {resetToCoM}"); + 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}"); cameraParent.transform.position = referenceTransform.position; cameraParent.transform.rotation = referenceTransform.rotation; flightCamera.SetTargetNone(); @@ -3651,6 +3661,7 @@ void SetCameraParent(Transform referenceTransform, bool resetToCoM = false) void SetDeathCam() { + if (DEBUG) Debug.Log($"[CameraTools]: Setting the death camera."); flightCamera.SetTargetNone(); flightCamera.transform.parent = deathCam.transform; cameraParentWasStolen = false; diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index e9318c99..9f1eeb8f 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,5 +1,6 @@ Improvements / Bugfixes: - Fix 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. v1.29.2 Improvements / Bugfixes: diff --git a/CameraTools/ModIntegration/BDArmory.cs b/CameraTools/ModIntegration/BDArmory.cs index ff0b18cc..961c0ce5 100644 --- a/CameraTools/ModIntegration/BDArmory.cs +++ b/CameraTools/ModIntegration/BDArmory.cs @@ -13,7 +13,7 @@ public class BDArmory : MonoBehaviour { #region Public fields public static BDArmory instance; - public bool hasBDA = false; + public static bool hasBDA = false; [CTPersistantField] public bool autoEnableForBDA = false; [CTPersistantField] public bool useBDAutoTarget = false; @@ -77,6 +77,11 @@ void Awake() void Start() { CheckForBDA(); + if (!hasBDA) + { + Destroy(this); + return; + } if (hasBDA) { aiModType = GetAIModuleType(); @@ -104,6 +109,11 @@ void OnDestroy() CTPersistantField.Save("BDArmoryIntegration", typeof(BDArmory), this); } + void FixedUpdate() + { + _inhibitChecked = false; + } + void CheckForBDA() { // This checks for the existence of a BDArmory assembly and picks out the BDACompetitionMode and VesselSpawner singletons. @@ -406,25 +416,30 @@ public void UpdateAIDogfightTarget() } } - public void AutoEnableForBDA() + public static bool IsInhibited => hasBDA && instance.isInhibited; + bool isInhibited // Avoid checking across mods more than once per fixedUpdate. { - if (!hasBDA) return; - try + get { - if (bdVesselsSpawningPropertyGetter != null && (bool)bdVesselsSpawningPropertyGetter(null)) + if (!_inhibitChecked) { - if (autoEnableOverride) - return; // Still spawning. - else - { - Debug.Log("[CameraTools]: Deactivating CameraTools while spawning vessels."); - autoEnableOverride = true; - camTools.RevertCamera(); - return; - } + if (bdVesselsSpawningPropertyGetter != null) _inhibited = (bool)bdVesselsSpawningPropertyGetter(null); + else if (bdVesselsSpawningFieldGetter != null) _inhibited = bdVesselsSpawningFieldGetter(bdVesselSpawnerInstance); // Deprecated, but just in case someone is using an older version of BDA. + else _inhibited = false; + _inhibitChecked = true; } + return _inhibited; + } + } + bool _inhibitChecked = false; + bool _inhibited = false; - if (bdVesselsSpawningFieldGetter != null && bdVesselsSpawningFieldGetter(bdVesselSpawnerInstance)) // Deprecated. + public void AutoEnableForBDA() + { + if (!hasBDA) return; + try + { + if (IsInhibited) { if (autoEnableOverride) return; // Still spawning. From 886ff530d18479624476379d757090a6a4138b4b Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 14 Nov 2023 00:54:06 +0100 Subject: [PATCH 191/263] Bump version number for release. --- .../Distribution/GameData/CameraTools/CameraTools.version | 4 ++-- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 3 ++- CameraTools/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index 87cda204..9f175021 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -5,8 +5,8 @@ "CHANGE_LOG_URL": "https://github.com/BrettRyland/CameraTools/blob/master/CameraTools/Distribution/GameData/CameraTools/Changelog.txt", "VERSION": { "MAJOR": 1, - "MINOR": 29, - "PATCH": 2, + "MINOR": 30, + "PATCH": 0, "BUILD": 0 }, "KSP_VERSION": { diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 9f1eeb8f..d85e8a36 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,5 +1,6 @@ +v1.30.0 Improvements / Bugfixes: -- Fix aero FX not being applied and provide a toggle to disable them. +- 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. v1.29.2 diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index 70bc4b1d..ed51777d 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.29.2.0" )] -[assembly: AssemblyFileVersion( "1.29.2" )] +[assembly: AssemblyVersion( "1.30.0.0" )] +[assembly: AssemblyFileVersion( "1.30.0" )] From a7e51abf732f25519aa84e571347fce714afa583 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 14 Nov 2023 00:55:17 +0100 Subject: [PATCH 192/263] Add missing changelog entry --- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index d85e8a36..bcde0598 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -2,6 +2,7 @@ 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: From 8811275b745e901debb44d0a6719a6e56c28b9cc Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 14 Nov 2023 23:26:54 +0100 Subject: [PATCH 193/263] Sanitise timestamps by shifting duplicates by 0.001s instead of breaking the view frustrum with NaNs and avoid division by 0 in spline calculations. --- CameraTools/CamTools.cs | 9 ++++---- CameraTools/CameraPath.cs | 23 ++++++++++++------- .../GameData/CameraTools/Changelog.txt | 4 ++++ CameraTools/SplineUtils.cs | 6 ++++- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 924ba376..43319610 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -1840,11 +1840,10 @@ void UpdatePathingCam() if (isPlayingPath) { - CameraTransformation tf = currentPath.Evaulate(pathTime * currentPath.timeScale); + CameraTransformation tf = currentPath.Evaluate(pathTime * currentPath.timeScale); 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 { @@ -2100,7 +2099,7 @@ void CreateNewKeyframe() cameraToolActive = true; } - currentPath.AddTransform(flightCamera.transform, zoomExp, time, positionInterpolationType, rotationInterpolationType); + currentPath.AddTransform(flightCamera.transform, zoomExp, ref time, positionInterpolationType, rotationInterpolationType); SelectKeyframe(currentPath.times.IndexOf(time)); @@ -2157,7 +2156,7 @@ void PlayPathingCam() StartPathingCam(); } - CameraTransformation firstFrame = currentPath.Evaulate(startTime); + CameraTransformation firstFrame = currentPath.Evaluate(startTime); flightCamera.transform.localPosition = firstFrame.position; flightCamera.transform.localRotation = firstFrame.rotation; SetZoomImmediate(firstFrame.zoom); @@ -3391,7 +3390,7 @@ void KeyframeEditorWindow() if (GUI.Button(new Rect(100 + gap, gap + (++line * lineHeight), 200 - 2 * gap, lineHeight - gap), "Apply")) { Debug.Log("[CameraTools]: Applying keyframe at time: " + currentKeyframeTime); - currentPath.SetTransform(currentKeyframeIndex, flightCamera.transform, zoomExp, currentKeyframeTime, currentKeyframePositionInterpolationType, currentKeyframeRotationInterpolationType); + currentPath.SetTransform(currentKeyframeIndex, flightCamera.transform, zoomExp, ref currentKeyframeTime, currentKeyframePositionInterpolationType, currentKeyframeRotationInterpolationType); applied = true; } if (GUI.Button(new Rect(100 + gap, gap + (++line * lineHeight), 200 - 2 * gap, lineHeight - gap), "Cancel")) diff --git a/CameraTools/CameraPath.cs b/CameraTools/CameraPath.cs index 04d9b048..82e274ee 100644 --- a/CameraTools/CameraPath.cs +++ b/CameraTools/CameraPath.cs @@ -159,11 +159,12 @@ public static List ParseEnumTypeList(string arrayString) where E : struct, return iList; } - public void AddTransform(Transform cameraTransform, float zoom, float time, PositionInterpolationType positionInterpolationType, RotationInterpolationType rotationInterpolationType) + public void AddTransform(Transform cameraTransform, float zoom, ref float time, PositionInterpolationType positionInterpolationType, RotationInterpolationType rotationInterpolationType) { points.Add(cameraTransform.localPosition); rotations.Add(cameraTransform.localRotation); zooms.Add(zoom); + while (times.Contains(time)) time += 1e-3f; // Avoid duplicate times. times.Add(time); positionInterpolationTypes.Add(positionInterpolationType); rotationInterpolationTypes.Add(rotationInterpolationType); @@ -171,11 +172,12 @@ public void AddTransform(Transform cameraTransform, float zoom, float time, Posi UpdateCurves(); } - public void SetTransform(int index, Transform cameraTransform, float zoom, float time, PositionInterpolationType positionInterpolationType, RotationInterpolationType rotationInterpolationType) + public void SetTransform(int index, Transform cameraTransform, float zoom, ref float time, PositionInterpolationType positionInterpolationType, RotationInterpolationType rotationInterpolationType) { 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; @@ -202,9 +204,12 @@ public void RemoveKeyframe(int index) public void Sort() { - List keyframes = new List(); + List keyframes = new(); + List _times = new(); for (int i = 0; i < points.Count; 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()); @@ -235,12 +240,14 @@ public void UpdateCurves() } } - 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/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index bcde0598..f96da3f5 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,7 @@ +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. diff --git a/CameraTools/SplineUtils.cs b/CameraTools/SplineUtils.cs index fc697edf..bd43f4d9 100644 --- a/CameraTools/SplineUtils.cs +++ b/CameraTools/SplineUtils.cs @@ -45,6 +45,7 @@ public static Vector3 EvaluateSpline(Vector3 point1, Vector3 slope1, Vector3 poi /// public static Vector3 EstimateSlope(Vector3 point0, Vector3 point1, Vector3 point2, float dt01, float dt12) { + if (dt01 == 0 || dt12 == 0) return Vector3.zero; return 0.5f * ((point2 - point1) / dt12 + (point1 - point0) / dt01); } @@ -57,6 +58,7 @@ public static Vector3 EstimateSlope(Vector3 point0, Vector3 point1, Vector3 poin /// public static Vector3 EstimateSlope(Vector3 point0, Vector3 point1, float dt) { + if (dt == 0) return Vector3.zero; return (point1 - point0) / dt; } #endregion @@ -108,6 +110,7 @@ public static Quaternion EvaluateSpline(Quaternion point1, Quaternion slope1, Qu /// public static Quaternion EstimateSlope(Quaternion point0, Quaternion point1, Quaternion point2, float dt01, float dt12) { + if (dt01 == 0 || dt12 == 0) return Quaternion.identity; return new Quaternion( 0.5f * ((point2.x - point1.x) / dt12 + (point1.x - point0.x) / dt01), 0.5f * ((point2.y - point1.y) / dt12 + (point1.y - point0.y) / dt01), @@ -125,6 +128,7 @@ public static Quaternion EstimateSlope(Quaternion point0, Quaternion point1, Qua /// public static Quaternion EstimateSlope(Quaternion point0, Quaternion point1, float dt) { + if (dt == 0) return Quaternion.identity; return new Quaternion( (point1.x - point0.x) / dt, (point1.y - point0.y) / dt, @@ -132,6 +136,6 @@ public static Quaternion EstimateSlope(Quaternion point0, Quaternion point1, flo (point1.w - point0.w) / dt ); } -#endregion + #endregion } } \ No newline at end of file From 24f468af020a76b581f433d379f5798288422e77 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 14 Nov 2023 23:27:35 +0100 Subject: [PATCH 194/263] Bump version number for bugfix release. --- .../Distribution/GameData/CameraTools/CameraTools.version | 2 +- CameraTools/Properties/AssemblyInfo.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index 9f175021..6a903064 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -6,7 +6,7 @@ "VERSION": { "MAJOR": 1, "MINOR": 30, - "PATCH": 0, + "PATCH": 1, "BUILD": 0 }, "KSP_VERSION": { diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index ed51777d..c9297d6d 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.30.0.0" )] -[assembly: AssemblyFileVersion( "1.30.0" )] +[assembly: AssemblyVersion( "1.30.1.0" )] +[assembly: AssemblyFileVersion( "1.30.1" )] From 874421c2fba47e4e7a2aed43753ab691916bea37 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 21 Nov 2023 23:16:25 +0100 Subject: [PATCH 195/263] Fix NREs from atmospheric audiosources when not in atmosphere. --- CameraTools/CTAtmosphericAudioController.cs | 10 +++++----- .../Distribution/GameData/CameraTools/Changelog.txt | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CameraTools/CTAtmosphericAudioController.cs b/CameraTools/CTAtmosphericAudioController.cs index 2f1db5a7..835198aa 100644 --- a/CameraTools/CTAtmosphericAudioController.cs +++ b/CameraTools/CTAtmosphericAudioController.cs @@ -99,11 +99,11 @@ void Awake() void Reset() { playedBoom = true; // Default to true so that it doesn't play accidentally. - if (windAudioSource.isPlaying) windAudioSource.Stop(); - if (windHowlAudioSource.isPlaying) windHowlAudioSource.Stop(); - if (windTearAudioSource.isPlaying) windTearAudioSource.Stop(); - if (sonicBoomSource.isPlaying) sonicBoomSource.Stop(); - if (delayedSonicBoomSource.isPlaying) delayedSonicBoomSource.Stop(); + 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() diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index f96da3f5..7c6fbeef 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,6 @@ +Improvements / Bugfixes: +- Fix NREs from atmospheric audiosources when not in atmosphere. + 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. From ca4b5f20c1883ec59da7d7e226a18b72175d29f1 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Tue, 28 Nov 2023 23:56:53 +0100 Subject: [PATCH 196/263] Minor tweaks to MouseAimFlight helpers. --- CameraTools/CamTools.cs | 14 +++++++------- CameraTools/ModIntegration/MouseAimFlight.cs | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 43319610..899a87fc 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -971,7 +971,7 @@ void StartDogfightCamera() DebugLog(message); } - if (MouseAimFlight.IsMouseAimActive()) + if (MouseAimFlight.IsMouseAimActive) { dogfightTarget = null; dogfightLastTarget = true; @@ -1050,13 +1050,13 @@ void UpdateDogfightCamera() } var cameraTransform = flightCamera.transform; - if (MouseAimFlight.IsMouseAimActive()) + 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(); + mouseAimFlightTarget = MouseAimFlight.GetMouseAimTarget; mouseAimFlightTargetLocal = cameraTransform.InverseTransformDirection(mouseAimFlightTarget); dogfightLastTargetPosition = (mouseAimFlightTarget.normalized + vessel.srf_vel_direction) * 5000f + vessel.CoM; } @@ -1160,7 +1160,7 @@ void UpdateDogfightCamera() if (freeLook) { 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 (MouseAimFlight.IsMouseAimActive) MouseAimFlight.SetFreeLookCooldown(1); // Give it 1s for the camera orientation to recover before resuming applying our modification to the MouseAimFlight target. } } if (freeLook) @@ -1175,7 +1175,7 @@ void UpdateDogfightCamera() Quaternion targetLook = Quaternion.LookRotation(dogfightLastTargetPosition - cameraTransform.position, dogfightCameraRollUp); Quaternion camRot = Quaternion.Lerp(vesselLook, targetLook, 0.5f); cameraTransform.rotation = Quaternion.Lerp(cameraTransform.rotation, camRot, dogfightLerp); - if (MouseAimFlight.IsMouseAimActive()) + if (MouseAimFlight.IsMouseAimActive) { if (!MouseAimFlight.IsInFreeLookRecovery) { @@ -2863,7 +2863,7 @@ void GuiWindow(int windowID) { GUI.Label(ThinRect(++line), "Secondary Target:"); string tVesselLabel; - if (MouseAimFlight.IsMouseAimActive()) + if (MouseAimFlight.IsMouseAimActive) { tVesselLabel = "MouseAimFlight"; } else if (showingVesselList) { tVesselLabel = "Clear"; } @@ -2888,7 +2888,7 @@ void GuiWindow(int windowID) } if (showingVesselList) { - if (MouseAimFlight.IsMouseAimActive()) showingVesselList = false; + if (MouseAimFlight.IsMouseAimActive) showingVesselList = false; foreach (var v in loadedVessels) { if (!v || !v.loaded) continue; diff --git a/CameraTools/ModIntegration/MouseAimFlight.cs b/CameraTools/ModIntegration/MouseAimFlight.cs index 2c4f42e1..0192717e 100644 --- a/CameraTools/ModIntegration/MouseAimFlight.cs +++ b/CameraTools/ModIntegration/MouseAimFlight.cs @@ -120,19 +120,19 @@ public bool IsMouseAimFlightActive() public Vector3 GetCurrentMouseAimTarget() { - if (!IsMouseAimActive()) return lastTarget; + if (!IsMouseAimActive) return lastTarget; lastTarget = targetPositionFieldGetter(mouseAimFlightInstance); return lastTarget; } public void SetCurrentMouseAimTarget(Vector3 position) { - if (!IsMouseAimActive()) return; + if (!IsMouseAimActive) return; targetPositionFieldSetter(mouseAimFlightInstance, position); } - public static bool IsMouseAimActive() => hasMouseAimFlight && Instance != null && Instance.IsMouseAimFlightActive(); - public static Vector3 GetMouseAimTarget() => Instance.GetCurrentMouseAimTarget(); + public static bool IsMouseAimActive => hasMouseAimFlight && Instance != null && Instance.IsMouseAimFlightActive(); + public static Vector3 GetMouseAimTarget => Instance.GetCurrentMouseAimTarget(); public static void SetMouseAimTarget(Vector3 position) => Instance.SetCurrentMouseAimTarget(position); // Free Look From 31f4ee8da65a62e47fdc3f3063d0c05e6024d63d Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 7 Dec 2023 01:50:39 +0100 Subject: [PATCH 197/263] Don't reset the original camera parent's position when reverting (fixes weird camera view). --- CameraTools/CamTools.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 899a87fc..a6bba41f 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -28,7 +28,6 @@ public class CamTools : MonoBehaviour Vector3 origLocalPosition; Quaternion origLocalRotation; Transform origParent; - Vector3 origParentPosition; float origNearClip; float origDistance; FlightCamera.Modes origMode; @@ -2472,7 +2471,6 @@ public void RevertCamera() flightCamera.transform.parent = origParent; if (origParent != null) // Restore the camera to the original local offsets from the original gameObject. { - origParent.position = origParentPosition; flightCamera.transform.localPosition = origLocalPosition; flightCamera.transform.localRotation = origLocalRotation; flightCamera.SetDistanceImmediate(origDistance); @@ -2521,11 +2519,11 @@ void SaveOriginalCamera() origLocalPosition = flightCamera.transform.localPosition; origLocalRotation = flightCamera.transform.localRotation; origParent = flightCamera.transform.parent; - origParentPosition = origParent.position; 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() From 14a2ae499f64e642cca0d7528af50c62208e1bac Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 7 Dec 2023 01:51:22 +0100 Subject: [PATCH 198/263] Add support for a new property in BDA for inhibiting camera tools. --- CameraTools/ModIntegration/BDArmory.cs | 37 +++++++++++++++++++------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/CameraTools/ModIntegration/BDArmory.cs b/CameraTools/ModIntegration/BDArmory.cs index 961c0ce5..608ee0f9 100644 --- a/CameraTools/ModIntegration/BDArmory.cs +++ b/CameraTools/ModIntegration/BDArmory.cs @@ -43,6 +43,7 @@ public List bdWMVessels object bdVesselSpawnerInstance = null; Func bdVesselsSpawningFieldGetter = null; Func bdVesselsSpawningPropertyGetter = null; + Func bdInhibitCameraToolsPropertyGetter = null; Type bdBDATournamentType = null; object bdBDATournamentInstance = null; Func bdTournamentWarpInProgressFieldGetter = null; @@ -123,6 +124,7 @@ void CheckForBDA() bdVesselSpawnerInstance = null; bdVesselsSpawningFieldGetter = null; bdVesselsSpawningPropertyGetter = null; + bdInhibitCameraToolsPropertyGetter = null; bdLoadedVesselSwitcherVesselsPropertyGetter = null; bdBDATournamentType = null; bdBDATournamentInstance = null; @@ -156,14 +158,17 @@ void CheckForBDA() } break; case "VesselSpawnerStatus": - foreach (var propertyInfo in t.GetProperties(BindingFlags.Public | BindingFlags.Static)) - if (propertyInfo != null && propertyInfo.Name == "inhibitCameraTools") - { - bdVesselsSpawningPropertyGetter = ReflectionUtils.BuildGetAccessor(propertyInfo.GetGetMethod()); - if (bdVesselsSpawningFieldGetter != null) // Clear the deprecated field. - { bdVesselsSpawningFieldGetter = null; } - break; - } + if (bdInhibitCameraToolsPropertyGetter == null) // Skip if we've found a more up-to-date property to access. + { + foreach (var propertyInfo in t.GetProperties(BindingFlags.Public | BindingFlags.Static)) + if (propertyInfo != null && propertyInfo.Name == "inhibitCameraTools") + { + bdVesselsSpawningPropertyGetter = ReflectionUtils.BuildGetAccessor(propertyInfo.GetGetMethod()); + if (bdVesselsSpawningFieldGetter != null) // Clear the deprecated field. + { bdVesselsSpawningFieldGetter = null; } + break; + } + } break; case "LoadedVesselSwitcher": bdLoadedVesselSwitcherInstance = FindObjectOfType(t); @@ -175,7 +180,7 @@ void CheckForBDA() } break; case "VesselSpawner": - if (bdVesselsSpawningPropertyGetter == null) + if (bdVesselsSpawningPropertyGetter == null && bdInhibitCameraToolsPropertyGetter == null) // Skip if we've found a more up-to-date property to access. { if (!t.IsSubclassOf(typeof(UnityEngine.Object))) continue; // In BDArmory v1.5.0 and upwards, VesselSpawner is a static class. bdVesselSpawnerInstance = FindObjectOfType(t); @@ -187,6 +192,17 @@ void CheckForBDA() } } break; + case "CameraTools": + foreach (var propertyInfo in t.GetProperties(BindingFlags.Public | BindingFlags.Static)) + if (propertyInfo != null && propertyInfo.Name == "InhibitCameraTools") + { + bdInhibitCameraToolsPropertyGetter = ReflectionUtils.BuildGetAccessor(propertyInfo.GetGetMethod()); + // Clear the deprecated fields. + bdVesselsSpawningFieldGetter = null; + bdVesselsSpawningPropertyGetter = null; + break; + } + break; case "BDATournament": bdBDATournamentType = t; bdBDATournamentInstance = FindObjectOfType(bdBDATournamentType); @@ -423,7 +439,8 @@ bool isInhibited // Avoid checking across mods more than once per fixedUpdate. { if (!_inhibitChecked) { - if (bdVesselsSpawningPropertyGetter != null) _inhibited = (bool)bdVesselsSpawningPropertyGetter(null); + if (bdInhibitCameraToolsPropertyGetter != null) _inhibited = (bool)bdInhibitCameraToolsPropertyGetter(null); + else if (bdVesselsSpawningPropertyGetter != null) _inhibited = (bool)bdVesselsSpawningPropertyGetter(null); // Deprecated in v1.6.9.0 of BDA. else if (bdVesselsSpawningFieldGetter != null) _inhibited = bdVesselsSpawningFieldGetter(bdVesselSpawnerInstance); // Deprecated, but just in case someone is using an older version of BDA. else _inhibited = false; _inhibitChecked = true; From 959b016dcf950ae447ed82d1214ee8255c8858c9 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 7 Dec 2023 14:41:40 +0100 Subject: [PATCH 199/263] Override the camera restore distance with custom distance (from BDA) when reverting due to auto-enabling for BDA. --- CameraTools/CamTools.cs | 5 ++- .../GameData/CameraTools/Changelog.txt | 4 ++ CameraTools/ModIntegration/BDArmory.cs | 38 +++++++++++++------ 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index a6bba41f..41c92407 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -2473,7 +2473,7 @@ public void RevertCamera() { flightCamera.transform.localPosition = origLocalPosition; flightCamera.transform.localRotation = origLocalRotation; - flightCamera.SetDistanceImmediate(origDistance); + 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). { @@ -2490,6 +2490,7 @@ public void RevertCamera() flightCamera.mainCamera.nearClipPlane = origNearClip; else Camera.main.nearClipPlane = origNearClip; + if (BDArmory.hasBDA) bdArmory.OnRevert(); flightCamera.ActivateUpdate(); @@ -2702,7 +2703,7 @@ void GuiWindow(int windowID) 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. } - bdArmory.ToggleInputFields(textInput); + if (BDArmory.hasBDA) bdArmory.ToggleInputFields(textInput); } line++; diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 7c6fbeef..517aa5f3 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,5 +1,9 @@ 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. +- Minor tweaks to MouseAimFlight helpers. v1.30.1 Improvements / Bugfixes: diff --git a/CameraTools/ModIntegration/BDArmory.cs b/CameraTools/ModIntegration/BDArmory.cs index 608ee0f9..4c1ec9cd 100644 --- a/CameraTools/ModIntegration/BDArmory.cs +++ b/CameraTools/ModIntegration/BDArmory.cs @@ -31,6 +31,7 @@ public List bdWMVessels return _bdWMVessels; } } + public float restoreDistanceLimit = float.MaxValue; // Limit the distance to restore the camera to (for auto-enabling). #endregion #region Private fields @@ -44,6 +45,7 @@ public List bdWMVessels Func bdVesselsSpawningFieldGetter = null; Func bdVesselsSpawningPropertyGetter = null; Func bdInhibitCameraToolsPropertyGetter = null; + Func bdRestoreDistanceLimitPropertyGetter = null; Type bdBDATournamentType = null; object bdBDATournamentInstance = null; Func bdTournamentWarpInProgressFieldGetter = null; @@ -125,6 +127,7 @@ void CheckForBDA() bdVesselsSpawningFieldGetter = null; bdVesselsSpawningPropertyGetter = null; bdInhibitCameraToolsPropertyGetter = null; + bdRestoreDistanceLimitPropertyGetter = null; bdLoadedVesselSwitcherVesselsPropertyGetter = null; bdBDATournamentType = null; bdBDATournamentInstance = null; @@ -194,13 +197,20 @@ void CheckForBDA() break; case "CameraTools": foreach (var propertyInfo in t.GetProperties(BindingFlags.Public | BindingFlags.Static)) - if (propertyInfo != null && propertyInfo.Name == "InhibitCameraTools") + if (propertyInfo != null) { - bdInhibitCameraToolsPropertyGetter = ReflectionUtils.BuildGetAccessor(propertyInfo.GetGetMethod()); - // Clear the deprecated fields. - bdVesselsSpawningFieldGetter = null; - bdVesselsSpawningPropertyGetter = null; - break; + switch (propertyInfo.Name) + { + case "InhibitCameraTools": + bdInhibitCameraToolsPropertyGetter = ReflectionUtils.BuildGetAccessor(propertyInfo.GetGetMethod()); + // Clear the deprecated fields. + bdVesselsSpawningFieldGetter = null; + bdVesselsSpawningPropertyGetter = null; + break; + case "RestoreDistanceLimit": + bdRestoreDistanceLimitPropertyGetter = ReflectionUtils.BuildGetAccessor(propertyInfo.GetGetMethod()); + break; + } } break; case "BDATournament": @@ -490,6 +500,7 @@ public void AutoEnableForBDA() { Debug.Log("[CameraTools]: Activating CameraTools for BDArmory competition as competition is starting."); camTools.cameraActivate(); + restoreDistanceLimit = bdRestoreDistanceLimitPropertyGetter != null ? (float)bdRestoreDistanceLimitPropertyGetter(null) : float.MaxValue; return; } else if (bdCompetitionIsActiveFieldGetter != null && bdCompetitionIsActiveFieldGetter(bdCompetitionInstance)) @@ -497,22 +508,25 @@ public void AutoEnableForBDA() Debug.Log("[CameraTools]: Activating CameraTools for BDArmory competition as competition is active."); UpdateAIDogfightTarget(); camTools.cameraActivate(); + restoreDistanceLimit = bdRestoreDistanceLimitPropertyGetter != null ? (float)bdRestoreDistanceLimitPropertyGetter(null) : float.MaxValue; return; } } catch (Exception e) { Debug.LogError("[CameraTools]: Checking competition state of BDArmory failed: " + e.Message); - bdCompetitionIsActiveFieldGetter = null; - bdCompetitionStartingFieldGetter = null; - bdCompetitionInstance = null; - bdVesselsSpawningFieldGetter = null; - bdVesselsSpawningPropertyGetter = null; - bdVesselSpawnerInstance = null; CheckForBDA(); } } + /// + /// Perform any BDA-related actions when the camera is reverted. + /// + public void OnRevert() + { + restoreDistanceLimit = float.MaxValue; // Reset the restore distance limit for manual store/restore. + } + FieldInfo GetThreatField() { if (wmModType == null) return null; From 040052fc5b64049404274e4be3bacc04ac3729be Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 7 Dec 2023 14:45:08 +0100 Subject: [PATCH 200/263] Adjust debug log tags for BDA mod-integration. --- CameraTools/ModIntegration/BDArmory.cs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/CameraTools/ModIntegration/BDArmory.cs b/CameraTools/ModIntegration/BDArmory.cs index 4c1ec9cd..50581442 100644 --- a/CameraTools/ModIntegration/BDArmory.cs +++ b/CameraTools/ModIntegration/BDArmory.cs @@ -377,7 +377,7 @@ Type GetAIModuleType() { if (t.Name == "BDGenericAIBase") { - if (CamTools.DEBUG) Debug.Log("[CameraTools]: Found BDGenericAIBase type."); + if (CamTools.DEBUG) Debug.Log("[CameraTools.ModIntegration.BDArmory]: Found BDGenericAIBase type."); return t; } } @@ -397,7 +397,7 @@ Type GetWeaponManagerType() { if (t.Name == "MissileFire") { - if (CamTools.DEBUG) Debug.Log("[CameraTools]: Found MissileFire type."); + if (CamTools.DEBUG) Debug.Log("[CameraTools.ModIntegration.BDArmory]: Found MissileFire type."); return t; } } @@ -472,7 +472,7 @@ public void AutoEnableForBDA() return; // Still spawning. else { - Debug.Log("[CameraTools]: Deactivating CameraTools while spawning vessels."); + Debug.Log("[CameraTools.ModIntegration.BDArmory]: Deactivating CameraTools while spawning vessels."); autoEnableOverride = true; camTools.RevertCamera(); return; @@ -485,7 +485,7 @@ public void AutoEnableForBDA() return; // Still warping. else { - Debug.Log("[CameraTools]: Deactivating CameraTools while warping between rounds."); + Debug.Log("[CameraTools.ModIntegration.BDArmory]: Deactivating CameraTools while warping between rounds."); autoEnableOverride = true; camTools.RevertCamera(); return; @@ -498,14 +498,14 @@ public void AutoEnableForBDA() if (vessel == null || (hasPilotAI && vessel.LandedOrSplashed)) return; // Don't activate for landed/splashed planes. if (bdCompetitionStartingFieldGetter != null && bdCompetitionStartingFieldGetter(bdCompetitionInstance)) { - Debug.Log("[CameraTools]: Activating CameraTools for BDArmory competition as competition is starting."); + Debug.Log("[CameraTools.ModIntegration.BDArmory]: Activating CameraTools for BDArmory competition as competition is starting."); camTools.cameraActivate(); restoreDistanceLimit = bdRestoreDistanceLimitPropertyGetter != null ? (float)bdRestoreDistanceLimitPropertyGetter(null) : float.MaxValue; return; } else if (bdCompetitionIsActiveFieldGetter != null && bdCompetitionIsActiveFieldGetter(bdCompetitionInstance)) { - Debug.Log("[CameraTools]: Activating CameraTools for BDArmory competition as competition is active."); + Debug.Log("[CameraTools.ModIntegration.BDArmory]: Activating CameraTools for BDArmory competition as competition is active."); UpdateAIDogfightTarget(); camTools.cameraActivate(); restoreDistanceLimit = bdRestoreDistanceLimitPropertyGetter != null ? (float)bdRestoreDistanceLimitPropertyGetter(null) : float.MaxValue; @@ -514,7 +514,7 @@ public void AutoEnableForBDA() } catch (Exception e) { - Debug.LogError("[CameraTools]: Checking competition state of BDArmory failed: " + e.Message); + Debug.LogError("[CameraTools.ModIntegration.BDArmory]: Checking competition state of BDArmory failed: " + e.Message); CheckForBDA(); } } @@ -537,7 +537,7 @@ FieldInfo GetThreatField() if (f.Name == "incomingThreatVessel") { bdWmThreatFieldGetter = ReflectionUtils.CreateGetter(f); - if (CamTools.DEBUG) Debug.Log($"[CameraTools]: Created bdWmThreatFieldGetter."); + if (CamTools.DEBUG) Debug.Log($"[CameraTools.ModIntegration.BDArmory]: Created bdWmThreatFieldGetter."); return f; } } @@ -555,7 +555,7 @@ FieldInfo GetMissileField() if (f.Name == "incomingMissileVessel") { bdWmMissileFieldGetter = ReflectionUtils.CreateGetter(f); - if (CamTools.DEBUG) Debug.Log($"[CameraTools]: Created bdWmMissileFieldGetter."); + if (CamTools.DEBUG) Debug.Log($"[CameraTools.ModIntegration.BDArmory]: Created bdWmMissileFieldGetter."); return f; } } @@ -573,7 +573,7 @@ FieldInfo GetUnderFireField() if (f.Name == "underFire") { bdWmUnderFireFieldGetter = ReflectionUtils.CreateGetter(f); - if (CamTools.DEBUG) Debug.Log($"[CameraTools]: Created bdWmUnderFireFieldGetter."); + if (CamTools.DEBUG) Debug.Log($"[CameraTools.ModIntegration.BDArmory]: Created bdWmUnderFireFieldGetter."); return f; } } @@ -591,7 +591,7 @@ FieldInfo GetUnderAttackField() if (f.Name == "underAttack") { bdWmUnderAttackFieldGetter = ReflectionUtils.CreateGetter(f); - if (CamTools.DEBUG) Debug.Log("[CameraTools]: Created bdWmUnderAttackFieldGetter."); + if (CamTools.DEBUG) Debug.Log("[CameraTools.ModIntegration.BDArmory]: Created bdWmUnderAttackFieldGetter."); return f; } } @@ -609,7 +609,7 @@ FieldInfo GetAITargetField() if (f.Name == "targetVessel") { bdAITargetFieldGetter = ReflectionUtils.CreateGetter(f); - if (CamTools.DEBUG) Debug.Log("[CameraTools]: Created bdAITargetFieldGetter."); + if (CamTools.DEBUG) Debug.Log("[CameraTools.ModIntegration.BDArmory]: Created bdAITargetFieldGetter."); return f; } } @@ -627,7 +627,7 @@ PropertyInfo GetRecentlyFiringProperty() if (p != null && p.Name == "recentlyFiring") { bdWmRecentlyFiringPropertyGetter = ReflectionUtils.BuildGetAccessor(p.GetGetMethod()); - if (CamTools.DEBUG) Debug.Log("[CameraTools]: Created bdWmRecentlyFiringPropertyGetter."); + if (CamTools.DEBUG) Debug.Log("[CameraTools.ModIntegration.BDArmory]: Created bdWmRecentlyFiringPropertyGetter."); return p; } } From 1dd3b11fe0d3e573149cf56cde7810a37ac4f139 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 8 Dec 2023 01:08:55 +0100 Subject: [PATCH 201/263] Add support for new BDA helpers for tracking missiles' targets in dogfight mode. Some internal restructuring of utils. Begin adding localisation support. Begin adding tooltips. --- CameraTools/CamTools.cs | 79 +++++++++++++++---- CameraTools/CameraTools.csproj | 17 ++-- .../GameData/CameraTools/Changelog.txt | 4 + .../CameraTools/Localization/en-us.cfg | 25 ++++++ CameraTools/ModIntegration/BDArmory.cs | 52 ++++++++++-- CameraTools/ModIntegration/MouseAimFlight.cs | 2 + CameraTools/RotationAnimation.cs | 4 +- CameraTools/{ => Utils}/CCInputUtils.cs | 2 +- CameraTools/{ => Utils}/MathUtils.cs | 2 +- CameraTools/{ => Utils}/ReflectionUtils.cs | 2 +- CameraTools/{ => Utils}/SplineUtils.cs | 2 +- CameraTools/Utils/StringUtils.cs | 30 +++++++ CameraTools/Vector3Animation.cs | 2 + 13 files changed, 189 insertions(+), 34 deletions(-) create mode 100644 CameraTools/Distribution/GameData/CameraTools/Localization/en-us.cfg rename CameraTools/{ => Utils}/CCInputUtils.cs (99%) rename CameraTools/{ => Utils}/MathUtils.cs (98%) rename CameraTools/{ => Utils}/ReflectionUtils.cs (99%) rename CameraTools/{ => Utils}/SplineUtils.cs (99%) create mode 100644 CameraTools/Utils/StringUtils.cs diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 41c92407..75aeacc7 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -7,6 +7,9 @@ using UnityEngine; using CameraTools.ModIntegration; +using CameraTools.Utils; + +using static CameraTools.Utils.StringUtils; // For shorter localisation namespace CameraTools { @@ -43,6 +46,9 @@ public class CamTools : MonoBehaviour 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 tooltip; + Rect tooltipRect = new(); string message; bool vesselSwitched = false; @@ -985,6 +991,11 @@ void StartDogfightCamera() { dogfightVelocityChase = false; } + else if (BDArmory.hasBDA && bdArmory.isBDMissile) + { + dogfightLastTarget = true; + dogfightVelocityChase = false; + } else { if (false && randomMode && rng.Next(3) == 0) @@ -1075,16 +1086,24 @@ void UpdateDogfightCamera() } else if (dogfightLastTarget) { - if (!FloatingOrigin.Offset.IsZero() || !Krakensbane.GetFrameVelocity().IsZero()) - { dogfightLastTargetPosition -= FloatingOrigin.OffsetNonKrakensbane; } - dogfightLastTargetPosition += dogfightLastTargetVelocity * TimeWarp.fixedDeltaTime; + if (BDArmory.hasBDA && bdArmory.isBDMissile) + { + dogfightLastTargetPosition = bdArmory.GetMissileTargetedPosition(); + if (dogfightLastTargetPosition == default) dogfightLastTarget = false; + } + else + { + if (!FloatingOrigin.Offset.IsZero() || !Krakensbane.GetFrameVelocity().IsZero()) + { dogfightLastTargetPosition -= FloatingOrigin.OffsetNonKrakensbane; } + dogfightLastTargetPosition += dogfightLastTargetVelocity * TimeWarp.fixedDeltaTime; + } } cameraParent.transform.position = vessel.CoM; if (dogfightVelocityChase) { var lastDogfightLastTargetPosition = dogfightLastTargetPosition; - if (vessel.Speed() > 1) + if (vessel.Speed() > 1 && !vessel.InOrbit()) { dogfightLastTargetPosition = vessel.CoM + vessel.Velocity().normalized * 5000f; } @@ -1344,7 +1363,7 @@ void UpdateDogfightCamera() } } - if (BDArmory.hasBDA && bdArmory.hasBDAI && (bdArmory.useBDAutoTarget || (bdArmory.useCentroid && bdArmory.bdWMVessels.Count < 2))) + if (BDArmory.hasBDA && (bdArmory.hasBDAI || bdArmory.isBDMissile) && (bdArmory.useBDAutoTarget || (bdArmory.useCentroid && bdArmory.bdWMVessels.Count < 2))) { bdArmory.UpdateAIDogfightTarget(); // Using delegates instead of reflection allows us to check every frame. if (vessel.LandedOrSplashed) @@ -2569,6 +2588,11 @@ void OnGUI() { PathSelectorWindow(); } + + if (ShowTooltips && !string.IsNullOrEmpty(tooltip)) + { + tooltipRect = GUILayout.Window(GUIUtility.GetControlID(FocusType.Passive), tooltipRect, ShowTooltip, "", GUIStyle.none); + } } if (DEBUG) { @@ -2624,8 +2648,8 @@ void GuiWindow(int windowID) { GUI.DragWindow(new Rect(0, 0, windowWidth, draggableHeight)); - GUI.Label(new Rect(0, contentTop, windowWidth, 40), "Camera Tools", titleStyle); - GUI.Label(new Rect(windowWidth / 2f, contentTop + 35f, windowWidth / 2f - leftIndent - entryHeight, entryHeight), $"Version: {Version}", watermarkStyle); + GUI.Label(new Rect(0, contentTop, windowWidth, 40), Localize("#LOC_CameraTools"), titleStyle); + GUI.Label(new Rect(windowWidth / 2f, contentTop + 35f, windowWidth / 2f - leftIndent - entryHeight, entryHeight), $"{Localize("#LOC_CameraTools_Version")}: {Version}", watermarkStyle); if (GUI.Toggle(new Rect(windowWidth - leftIndent - 14f, contentTop + 31f, 20f, 20f), cameraToolActive, "") != cameraToolActive) { if (cameraToolActive) @@ -2648,18 +2672,23 @@ void GuiWindow(int windowID) float parseResult; //tool mode switcher - GUI.Label(LabelRect(++line), "Tool: " + toolMode.ToString(), leftLabelBold); - if (GUI.Button(new Rect(leftIndent, contentTop + (++line * entryHeight), 25, entryHeight - 2), "<")) + GUI.Label(LabelRect(++line), $"{Localize("#LOC_CameraTools_Tool")}: {toolMode}", leftLabelBold); + if (GUI.Button(new Rect(leftIndent, contentTop + (++line * entryHeight), 25, entryHeight - 2), LocTip("#LOC_CameraTools_PrevMode"))) { CycleToolMode(false); if (cameraToolActive) cameraActivate(); } - if (GUI.Button(new Rect(leftIndent + 25 + 4, contentTop + (line * entryHeight), 25, entryHeight - 2), ">")) + if (GUI.Button(new Rect(leftIndent + 25 + 4, contentTop + (line * entryHeight), 25, entryHeight - 2), LocTip("#LOC_CameraTools_NextMode"))) { CycleToolMode(true); if (cameraToolActive) cameraActivate(); } - if (GUI.Button(new Rect(windowWidth - leftIndent - 25, contentTop + (line * entryHeight), 25, entryHeight - 2), "#", textInput ? GUI.skin.box : GUI.skin.button)) + if (GUI.Button(new Rect(windowWidth - leftIndent - 54, contentTop + (line * entryHeight), 25, entryHeight - 2), Localize("#LOC_CameraTools_ShowTooltips"), ShowTooltips ? GUI.skin.box : GUI.skin.button)) + { + ShowTooltips = !ShowTooltips; + if (!ShowTooltips) tooltip = ""; + } + if (GUI.Button(new Rect(windowWidth - leftIndent - 25, contentTop + (line * entryHeight), 25, entryHeight - 2), LocTip("#LOC_CameraTools_ToggleTextFields"), textInput ? GUI.skin.box : GUI.skin.button)) { textInput = !textInput; if (!textInput) // Set the fields to their currently showing values. @@ -2707,17 +2736,17 @@ void GuiWindow(int windowID) } line++; - useAudioEffects = GUI.Toggle(LabelRect(++line), useAudioEffects, "Enable Audio Effects"); - if (enableVFX != (enableVFX = GUI.Toggle(LabelRect(++line), enableVFX, "Enable Visual Effects"))) + useAudioEffects = GUI.Toggle(LabelRect(++line), useAudioEffects, LocTip("#LOC_CameraTools_AudioEffects")); + if (enableVFX != (enableVFX = GUI.Toggle(LabelRect(++line), enableVFX, LocTip("#LOC_CameraTools_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, "Auto-Enable for BDArmory"); + if (BDArmory.hasBDA) bdArmory.autoEnableForBDA = GUI.Toggle(LabelRect(++line), bdArmory.autoEnableForBDA, LocTip("#LOC_CameraTools_AutoEnableForBDA")); line++; if (autoFOV && toolMode != ToolModes.Pathing) { - GUI.Label(LeftRect(++line), "Autozoom Margin: "); + GUI.Label(LeftRect(++line), LocTip("#LOC_CameraTools_AutozoomMargin")); if (!textInput) { autoZoomMargin = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + ((++line) * entryHeight), contentWidth - 45, entryHeight), autoZoomMargin, 0, autoZoomMarginMax); @@ -3311,6 +3340,26 @@ void GuiWindow(int windowID) //fix length 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 && tooltip != GUI.tooltip) + { + tooltip = GUI.tooltip; // Store the tooltip so we can show it externally to the window. + new GUIContent(GUI.tooltip); + tooltipRect.position = Event.current.mousePosition + windowRect.position; + var lines = tooltip.Split('\n'); + tooltipRect.width = lines.Max(l => GUI.skin.box.CalcSize(new GUIContent(l)).x); + tooltipRect.height = entryHeight * lines.Length; + tooltipRect.x = Mathf.Clamp(tooltipRect.x - tooltipRect.width - 20, 5, Screen.width - tooltipRect.width - 5); + tooltipRect.y = Mathf.Clamp(tooltipRect.y - tooltipRect.height - 10, 5, Screen.height - tooltipRect.height - 5); + } + } + + void ShowTooltip(int windowID) + { + GUILayout.BeginVertical(); + GUILayout.Label(tooltip, GUI.skin.box); + GUILayout.EndVertical(); } string KeyBinding(string current, string label, float line) diff --git a/CameraTools/CameraTools.csproj b/CameraTools/CameraTools.csproj index e59bf692..6324d775 100644 --- a/CameraTools/CameraTools.csproj +++ b/CameraTools/CameraTools.csproj @@ -52,7 +52,6 @@ - @@ -62,17 +61,19 @@ - - - - - - - + + + + + + + + + diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 517aa5f3..60505128 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -3,7 +3,11 @@ Improvements / Bugfixes: - 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. +- Begin adding tooltips. v1.30.1 Improvements / Bugfixes: 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..264c91f2 --- /dev/null +++ b/CameraTools/Distribution/GameData/CameraTools/Localization/en-us.cfg @@ -0,0 +1,25 @@ +Localization +{ + en-us + { + #LOC_CameraTools = 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_ToggleTextFields = # + #LOC_CameraTools_ToggleTextFields.tip = Toggle text fields + #LOC_CameraTools_AudioEffects = Enable Audio Effects + #LOC_CameraTools_AudioEffects.tip = Enable atmospheric audio effects\nsuch 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\nwhen BDA starts competitions + + #LOC_CameraTools_AutozoomMargin = Autozoom Margin: + #LOC_CameraTools_AutozoomMargin.tip = How much to automatically adjust the zoom. + } +} \ No newline at end of file diff --git a/CameraTools/ModIntegration/BDArmory.cs b/CameraTools/ModIntegration/BDArmory.cs index 50581442..6dd8415a 100644 --- a/CameraTools/ModIntegration/BDArmory.cs +++ b/CameraTools/ModIntegration/BDArmory.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; using System.Linq; -using System.IO; using System.Reflection; using System; using UnityEngine; +using CameraTools.Utils; namespace CameraTools.ModIntegration { @@ -46,6 +46,8 @@ public List bdWMVessels Func bdVesselsSpawningPropertyGetter = null; Func bdInhibitCameraToolsPropertyGetter = null; Func bdRestoreDistanceLimitPropertyGetter = null; + Func bdMissileTargetVesselPropertyGetter = null; + Func bdMissileTargetPositionPropertyGetter = null; Type bdBDATournamentType = null; object bdBDATournamentInstance = null; Func bdTournamentWarpInProgressFieldGetter = null; @@ -128,6 +130,8 @@ void CheckForBDA() bdVesselsSpawningPropertyGetter = null; bdInhibitCameraToolsPropertyGetter = null; bdRestoreDistanceLimitPropertyGetter = null; + bdMissileTargetVesselPropertyGetter = null; + bdMissileTargetPositionPropertyGetter = null; bdLoadedVesselSwitcherVesselsPropertyGetter = null; bdBDATournamentType = null; bdBDATournamentInstance = null; @@ -210,6 +214,12 @@ void CheckForBDA() case "RestoreDistanceLimit": bdRestoreDistanceLimitPropertyGetter = ReflectionUtils.BuildGetAccessor(propertyInfo.GetGetMethod()); break; + case "MissileTargetVessel": + bdMissileTargetVesselPropertyGetter = ReflectionUtils.BuildGetAccessor(propertyInfo.GetGetMethod()); + break; + case "MissileTargetPosition": + bdMissileTargetPositionPropertyGetter = ReflectionUtils.BuildGetAccessor(propertyInfo.GetGetMethod()); + break; } } break; @@ -284,6 +294,8 @@ public void CheckForBDMissile(Vessel v) if (p.GetComponent("MissileLauncher") || p.GetComponent("BDModularGuidance")) { isBDMissile = true; + AItargetUpdateTime = 0; // Reset the AI target update time so that it'll immediately check for a new target. + camTools.dogfightTarget = null; // Also, clear the dogfight target so that the debug messages are correct. return; } } @@ -367,6 +379,18 @@ Vessel GetAITargetedVessel() return null; } + Vessel GetMissileTargetedVessel() + { + if (!isBDMissile || bdMissileTargetVesselPropertyGetter == null) return null; + return (Vessel)bdMissileTargetVesselPropertyGetter(null); + } + + public Vector3 GetMissileTargetedPosition() + { + if (!isBDMissile || bdMissileTargetPositionPropertyGetter == null) return default; + return (Vector3)bdMissileTargetPositionPropertyGetter(null); + } + Type GetAIModuleType() { foreach (var assy in AssemblyLoader.loadedAssemblies) @@ -432,12 +456,28 @@ public void UpdateAIDogfightTarget() } else if (isBDMissile && useBDAutoTarget) { - camTools.dogfightTarget = null; - AItargetUpdateTime = Time.time; - if (CamTools.DEBUG) + if (Time.time - AItargetUpdateTime >= 0.1f) // Only update the missile's target vessel at most every 0.1s (gets reset on vessel switches). { - var message = "Current vessel is a missile, using dogfight chase mode."; - CamTools.DebugLog(message); + newAITarget = GetMissileTargetedVessel(); + if (CamTools.DEBUG) + { + if (newAITarget == null) + { + if (camTools.dogfightTarget) + { + var position = GetMissileTargetedPosition(); + if (position == default) CamTools.DebugLog("Missile has no target, using dogfight chase mode."); + else CamTools.DebugLog($"Missile is targeting position {position}."); + } + } + else if (newAITarget != camTools.dogfightTarget) + { + if (camTools.dogfightTarget) CamTools.DebugLog($"Missile switched target to {newAITarget.vesselName} from {camTools.dogfightTarget.vesselName}"); + else CamTools.DebugLog($"Missile is targeting {newAITarget.vesselName}"); + } + } + camTools.dogfightTarget = newAITarget; + AItargetUpdateTime = Time.time; } } } diff --git a/CameraTools/ModIntegration/MouseAimFlight.cs b/CameraTools/ModIntegration/MouseAimFlight.cs index 0192717e..9d30ddc3 100644 --- a/CameraTools/ModIntegration/MouseAimFlight.cs +++ b/CameraTools/ModIntegration/MouseAimFlight.cs @@ -2,6 +2,8 @@ using System; using System.Reflection; +using CameraTools.Utils; + namespace CameraTools.ModIntegration { [KSPAddon(KSPAddon.Startup.Flight, false)] diff --git a/CameraTools/RotationAnimation.cs b/CameraTools/RotationAnimation.cs index aad8234d..6b2a5748 100644 --- a/CameraTools/RotationAnimation.cs +++ b/CameraTools/RotationAnimation.cs @@ -1,5 +1,7 @@ using UnityEngine; -using System; + +using CameraTools.Utils; + namespace CameraTools { public enum RotationInterpolationType diff --git a/CameraTools/CCInputUtils.cs b/CameraTools/Utils/CCInputUtils.cs similarity index 99% rename from CameraTools/CCInputUtils.cs rename to CameraTools/Utils/CCInputUtils.cs index 6a745a0a..b756957b 100644 --- a/CameraTools/CCInputUtils.cs +++ b/CameraTools/Utils/CCInputUtils.cs @@ -10,7 +10,7 @@ using System; using UnityEngine; -namespace CameraTools +namespace CameraTools.Utils { public class CCInputUtils { diff --git a/CameraTools/MathUtils.cs b/CameraTools/Utils/MathUtils.cs similarity index 98% rename from CameraTools/MathUtils.cs rename to CameraTools/Utils/MathUtils.cs index a1958e76..79d176c2 100644 --- a/CameraTools/MathUtils.cs +++ b/CameraTools/Utils/MathUtils.cs @@ -1,7 +1,7 @@ using UnityEngine; using System.Globalization; -namespace CameraTools +namespace CameraTools.Utils { public static class MathUtils { diff --git a/CameraTools/ReflectionUtils.cs b/CameraTools/Utils/ReflectionUtils.cs similarity index 99% rename from CameraTools/ReflectionUtils.cs rename to CameraTools/Utils/ReflectionUtils.cs index 742037ac..82509177 100644 --- a/CameraTools/ReflectionUtils.cs +++ b/CameraTools/Utils/ReflectionUtils.cs @@ -3,7 +3,7 @@ using System.Reflection; using System.Reflection.Emit; -namespace CameraTools +namespace CameraTools.Utils { /// /// Using delegates to speed up reflection for frequently accessed properties and fields. diff --git a/CameraTools/SplineUtils.cs b/CameraTools/Utils/SplineUtils.cs similarity index 99% rename from CameraTools/SplineUtils.cs rename to CameraTools/Utils/SplineUtils.cs index bd43f4d9..32f7c9fb 100644 --- a/CameraTools/SplineUtils.cs +++ b/CameraTools/Utils/SplineUtils.cs @@ -1,6 +1,6 @@ using UnityEngine; -namespace CameraTools +namespace CameraTools.Utils { public static class SplineUtils { diff --git a/CameraTools/Utils/StringUtils.cs b/CameraTools/Utils/StringUtils.cs new file mode 100644 index 00000000..abb70887 --- /dev/null +++ b/CameraTools/Utils/StringUtils.cs @@ -0,0 +1,30 @@ +using KSP.Localization; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace CameraTools.Utils +{ + public static class StringUtils + { + static readonly Dictionary localizedStrings = new(); // Cache localized strings so that they don't need to be repeatedly localized. + + public static string Localize(string template) + { + if (!localizedStrings.TryGetValue(template, out string result)) + { + result = Localizer.Format(template); + localizedStrings[template] = result; + } + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static GUIContent LocTip(string template) => LocalizeWithToolTip(template); // Shortcut for convenience + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static GUIContent LocalizeWithToolTip(string template) + { + return new GUIContent(Localize(template), Localize($"{template}.tip")); + } + } +} \ No newline at end of file diff --git a/CameraTools/Vector3Animation.cs b/CameraTools/Vector3Animation.cs index 2050fefa..6d3f318e 100644 --- a/CameraTools/Vector3Animation.cs +++ b/CameraTools/Vector3Animation.cs @@ -1,5 +1,7 @@ using UnityEngine; +using CameraTools.Utils; + namespace CameraTools { public enum PositionInterpolationType { Linear, CubicSpline }; From ce8ca51eba46178000495df2566b3264ab88746a Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Fri, 8 Dec 2023 16:11:18 +0100 Subject: [PATCH 202/263] When missiles have no target vessel or position, revert to velocity chase mode properly. --- CameraTools/CamTools.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 75aeacc7..dba02584 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -1088,8 +1088,13 @@ void UpdateDogfightCamera() { if (BDArmory.hasBDA && bdArmory.isBDMissile) { - dogfightLastTargetPosition = bdArmory.GetMissileTargetedPosition(); - if (dogfightLastTargetPosition == default) dogfightLastTarget = false; + 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; } } else { From 5df4377d3f825f2c2840201107dc5e85af161834 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Sun, 17 Dec 2023 00:48:10 +0100 Subject: [PATCH 203/263] Update changelog. Bump version number for release. --- .../Distribution/GameData/CameraTools/CameraTools.version | 4 ++-- CameraTools/Distribution/GameData/CameraTools/Changelog.txt | 5 +++-- CameraTools/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version index 6a903064..d6951d1b 100644 --- a/CameraTools/Distribution/GameData/CameraTools/CameraTools.version +++ b/CameraTools/Distribution/GameData/CameraTools/CameraTools.version @@ -5,8 +5,8 @@ "CHANGE_LOG_URL": "https://github.com/BrettRyland/CameraTools/blob/master/CameraTools/Distribution/GameData/CameraTools/Changelog.txt", "VERSION": { "MAJOR": 1, - "MINOR": 30, - "PATCH": 1, + "MINOR": 31, + "PATCH": 0, "BUILD": 0 }, "KSP_VERSION": { diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 60505128..8a5bf3cd 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,4 @@ +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). @@ -6,8 +7,8 @@ Improvements / Bugfixes: - 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. -- Begin adding tooltips. +- Begin adding localisation support (not yet complete). +- Begin adding tooltips (not yet complete). v1.30.1 Improvements / Bugfixes: diff --git a/CameraTools/Properties/AssemblyInfo.cs b/CameraTools/Properties/AssemblyInfo.cs index c9297d6d..ae8d4cc6 100644 --- a/CameraTools/Properties/AssemblyInfo.cs +++ b/CameraTools/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion( "1.30.1.0" )] -[assembly: AssemblyFileVersion( "1.30.1" )] +[assembly: AssemblyVersion( "1.31.0.0" )] +[assembly: AssemblyFileVersion( "1.31.0" )] From 6d1370a3fad7f2509662f1a575b737a49a9ecb10 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 28 Dec 2023 00:55:00 +0100 Subject: [PATCH 204/263] Improvements to death camera angles for dogfight mode. --- CameraTools/CamTools.cs | 95 ++++++++++++++----- .../GameData/CameraTools/Changelog.txt | 3 + 2 files changed, 76 insertions(+), 22 deletions(-) diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index dba02584..108f0a4c 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -152,6 +152,9 @@ enum fmModeTypes { Position, Speed }; //retaining position and rotation after vessel destruction GameObject deathCam; 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; @@ -528,7 +531,7 @@ void KrakensbaneWarpCorrection() // - 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) return; + if (vessel == null || !vessel.gameObject.activeInHierarchy) return; if (cameraToolActive) { var inHighWarp = (TimeWarp.WarpMode == TimeWarp.Modes.HIGH && TimeWarp.CurrentRate > 1); @@ -754,19 +757,22 @@ void Update() if (BDArmory.IsInhibited) return; // Don't do anything else while BDA is inhibiting us. if (cameraToolActive) { - switch (toolMode) + if (!hasDied) { - 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; + 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. } @@ -803,10 +809,18 @@ void FixedUpdate() RevertCamera(); } } + else if (hasDied) + { + deathCamVelocity = (deathCamVelocity - deathCamTargetVelocity) * deathCamDecayFactor + deathCamTargetVelocity; // Slow down to the target velocity. + deathCam.transform.position += deathCamVelocity * TimeWarp.fixedDeltaTime; + if (toolMode == ToolModes.DogfightCamera && deathCamTarget && deathCamTarget.gameObject.activeInHierarchy) + { + flightCamera.transform.rotation = Quaternion.Slerp(flightCamera.transform.rotation, Quaternion.LookRotation(deathCamTarget.transform.position - deathCam.transform.position, cameraUp), dogfightLerp / 2f); + } + return; // Do nothing else until we have an active vessel. + } } - if (hasDied && cameraToolActive) return; // Do nothing until we have an active vessel. - if (vessel == null || vessel != FlightGlobals.ActiveVessel) { vessel = FlightGlobals.ActiveVessel; @@ -892,8 +906,6 @@ void LateUpdate() if (BDArmory.IsInhibited) return; // Don't do anything else while BDA is inhibiting us. if (hasDied && cameraToolActive) { - deathCam.transform.position += deathCamVelocity * TimeWarp.deltaTime; - deathCamVelocity *= 0.95f; 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. { SetDeathCam(); @@ -2216,7 +2228,6 @@ void UpdateCameraShake() 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; } @@ -2389,6 +2400,7 @@ void SwitchToVessel(Vessel v) cockpitView = false; cockpits.Clear(); engines.Clear(); + hasDied = false; if (BDArmory.hasBDA) { @@ -3597,16 +3609,55 @@ void CycleToolMode(bool forward) #region Utils void CurrentVesselWillDestroy(Vessel v) { - if (vessel == v && cameraToolActive) + if (!hasDied && cameraToolActive && vessel == v) { hasDied = true; diedTime = Time.time; + deathCamTarget = null; - // Something borks the camera position/rotation when the target/parent is set to none/null. This fixes that. - deathCamVelocity = (vessel.radarAltitude > 10d ? vessel.Velocity() : Vector3d.zero) / 2f; // Track the explosion a bit. + if (toolMode == ToolModes.DogfightCamera) + { + // 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.transform.position - dogfightTarget.transform.position).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) + { + 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) { - message = $"Activating death camera with speed {deathCamVelocity.magnitude:G3}m/s."; Debug.Log("[CameraTools]: " + message); DebugLog(message); } diff --git a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt index 8a5bf3cd..35a93cd0 100644 --- a/CameraTools/Distribution/GameData/CameraTools/Changelog.txt +++ b/CameraTools/Distribution/GameData/CameraTools/Changelog.txt @@ -1,3 +1,6 @@ +Improvements / Bugfixes: +- Improvements to death camera angles for dogfight mode. + v1.31.0 Improvements / Bugfixes: - Fix NREs from atmospheric audiosources when not in atmosphere. From 0d6609dd7378d21fd60fa293ae7d837e107bfeb7 Mon Sep 17 00:00:00 2001 From: Brett Ryland Date: Thu, 4 Jan 2024 11:58:21 +0100 Subject: [PATCH 205/263] 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. Finish adding English (en-us) localisation and tooltips. --- CameraTools/CamTools.cs | 639 +++++++++--------- CameraTools/CameraTools.csproj | 8 +- .../GameData/CameraTools/Changelog.txt | 6 + .../CameraTools/Localization/en-us.cfg | 148 +++- CameraTools/Utils/StringUtils.cs | 39 +- CameraTools/Utils/Tooltips.cs | 66 ++ TODO.md | 5 + 7 files changed, 570 insertions(+), 341 deletions(-) create mode 100644 CameraTools/Utils/Tooltips.cs create mode 100644 TODO.md diff --git a/CameraTools/CamTools.cs b/CameraTools/CamTools.cs index 108f0a4c..4ecb4a88 100644 --- a/CameraTools/CamTools.cs +++ b/CameraTools/CamTools.cs @@ -9,7 +9,7 @@ using CameraTools.ModIntegration; using CameraTools.Utils; -using static CameraTools.Utils.StringUtils; // For shorter localisation +using static CameraTools.Utils.StringUtils; // For direct access to Localize and LocalizeStr namespace CameraTools { @@ -47,8 +47,6 @@ public class CamTools : MonoBehaviour [CTPersistantField] public static bool DEBUG = false; [CTPersistantField] public static bool DEBUG2 = false; [CTPersistantField] public static bool ShowTooltips = false; - string tooltip; - Rect tooltipRect = new(); string message; bool vesselSwitched = false; @@ -398,9 +396,9 @@ void Start() if (FlightGlobals.ActiveVessel != null) { - cameraParent.transform.position = FlightGlobals.ActiveVessel.transform.position; vessel = FlightGlobals.ActiveVessel; - deathCam.transform.position = vessel.transform.position; + cameraParent.transform.position = vessel.CoM; + deathCam.transform.position = vessel.CoM; deathCam.transform.rotation = vessel.transform.rotation; } GameEvents.onVesselChange.Add(SwitchToVessel); @@ -1023,12 +1021,9 @@ void StartDogfightCamera() dogfightPrevTarget = dogfightTarget; hasDied = false; - cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.CoM).normalized; + cameraUp = vessel.up; - if (flightCamera.transform.parent != cameraParent.transform) - { - SetCameraParent(deathCam.transform, true); // First update the cameraParent to the last deathCam configuration - } + SetCameraParent(deathCam.transform, true); // First update the cameraParent to the last deathCam configuration offset for the active vessel's CoM. cameraToolActive = true; @@ -1415,18 +1410,14 @@ void StartStationaryCamera() } hasDied = false; vessel = FlightGlobals.ActiveVessel; - cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.GetWorldPos3D()).normalized; + 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.upAxis).normalized; - stationaryCameraRoll = Quaternion.identity; + rightAxis = -Vector3.Cross(vessel.Velocity(), vessel.up).normalized; - if (flightCamera.transform.parent != cameraParent.transform) - { - SetCameraParent(deathCam.transform, true); // First update the cameraParent to the last deathCam configuration - } + SetCameraParent(deathCam.transform, true); // First update the cameraParent to the last deathCam configuration offset for the active vessel's CoM. manualPosition = Vector3.zero; if (randomMode) @@ -1450,9 +1441,9 @@ void StartStationaryCamera() float distanceAhead = Mathf.Clamp(4 * clampedSpeed, 30, 3500) * Mathf.Sign(maxRelV); if (vessel.Velocity().sqrMagnitude > 1) - { flightCamera.transform.position = vessel.transform.position + distanceAhead * vessel.Velocity().normalized; } + { flightCamera.transform.position = vessel.CoM + distanceAhead * vessel.Velocity().normalized; } else - { flightCamera.transform.position = vessel.transform.position + distanceAhead * vessel.vesselTransform.up; } + { flightCamera.transform.position = vessel.CoM + distanceAhead * vessel.vesselTransform.up; } if (flightCamera.mode == FlightCamera.Modes.FREE || FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.FREE) { @@ -1482,8 +1473,8 @@ void StartStationaryCamera() RaycastHit hit; do { - ray = new Ray(flightCamera.transform.position, vessel.transform.position - flightCamera.transform.position); - if (Physics.Raycast(ray, out hit, (flightCamera.transform.position - vessel.transform.position).magnitude, 1 << 15)) // Just terrain. + ray = new Ray(flightCamera.transform.position, vessel.CoM - flightCamera.transform.position); + if (Physics.Raycast(ray, out hit, (flightCamera.transform.position - vessel.CoM).magnitude, 1 << 15)) // Just terrain. { flightCamera.transform.position += 50f * cameraUp; // Try 50m higher. } @@ -1501,9 +1492,9 @@ void StartStationaryCamera() float distanceAhead = manualOffsetForward; if (vessel.Velocity().sqrMagnitude > 1) - { flightCamera.transform.position = vessel.transform.position + distanceAhead * vessel.Velocity().normalized; } + { flightCamera.transform.position = vessel.CoM + distanceAhead * vessel.Velocity().normalized; } else - { flightCamera.transform.position = vessel.transform.position + distanceAhead * vessel.vesselTransform.up; } + { flightCamera.transform.position = vessel.CoM + distanceAhead * vessel.vesselTransform.up; } if (flightCamera.mode == FlightCamera.Modes.FREE || FlightCamera.GetAutoModeForVessel(vessel) == FlightCamera.Modes.FREE) { @@ -1522,7 +1513,9 @@ void StartStationaryCamera() // Camera rotation. if (hasTarget) - { flightCamera.transform.rotation = Quaternion.LookRotation(vessel.transform.position - flightCamera.transform.position, cameraUp); } + { + flightCamera.transform.rotation = Quaternion.LookRotation(vessel.CoM - flightCamera.transform.position, cameraUp); + } // Initial velocity initialVelocity = vessel.Velocity(); @@ -1541,6 +1534,7 @@ void StartStationaryCamera() Debug.Log("[CameraTools]: Stationary Camera failed. Active Vessel is null."); } if (hasSavedRotation) { flightCamera.transform.rotation = savedRotation; } + stationaryCameraRoll = Quaternion.FromToRotation(Vector3.ProjectOnPlane(cameraUp, flightCamera.transform.forward), flightCamera.transform.up); } /// @@ -1554,11 +1548,11 @@ 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.transform.position + velForwardAxis * manualOffsetForward + velRightAxis * manualOffsetRight; + 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.transform.position).magnitude; + var gravity = -FlightGlobals.getGeeForceAtPosition(vessel.CoM).magnitude; int count = 0; float velOffset = 0; float lastVelOffset = velOffset; @@ -1567,7 +1561,7 @@ bool GetAutoLandingPosition() 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.transform.position + velForwardAxis * (manualOffsetForward + velOffset) + velRightAxis * manualOffsetRight; + 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). } @@ -1615,7 +1609,7 @@ void UpdateStationaryCamera() } lastVesselCoM = vessel.CoM; cameraParent.transform.position = manualPosition + vessel.CoM; - if (!randomMode && 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). Also, ignore this when using random mode. + 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; } @@ -1641,7 +1635,7 @@ void UpdateStationaryCamera() // if (DEBUG2 && !GameIsPaused) // { - // var Δ = lastOffset - (vessel.transform.position - flightCamera.transform.position); + // var Δ = lastOffset - (vessel.CoM - flightCamera.transform.position); // Debug2Log("situation: " + vessel.situation); // Debug2Log("warp mode: " + TimeWarp.WarpMode + ", fixedDeltaTime: " + TimeWarp.fixedDeltaTime); // Debug2Log("floating origin offset: " + FloatingOrigin.Offset.ToString("G6")); @@ -1662,7 +1656,7 @@ void UpdateStationaryCamera() // Debug2Log("δ: " + lastOffsetSinceLastFrame.ToString("G6")); // Debug2Log("Δ: " + Δ.ToString("G6")); // Debug2Log("δ + Δ: " + (lastOffsetSinceLastFrame + Δ).ToString("G6")); - // lastOffset = vessel.transform.position - flightCamera.transform.position; + // lastOffset = vessel.CoM - flightCamera.transform.position; // lastCamParentPosition = cameraParent.transform.position; // } @@ -1765,11 +1759,7 @@ void UpdateStationaryCamera() break; } - if (!string.IsNullOrEmpty(resetRollKey) && Input.GetKeyDown(resetRollKey)) - { - stationaryCameraRoll = Quaternion.identity; - flightCamera.transform.rotation = Quaternion.LookRotation(flightCamera.transform.forward, stationaryCameraRoll * cameraUp); - } + if (!string.IsNullOrEmpty(resetRollKey) && Input.GetKeyDown(resetRollKey)) ResetRoll(); } if (Input.GetKey(KeyCode.Mouse1) && Input.GetKey(KeyCode.Mouse2)) @@ -1857,14 +1847,14 @@ void StartPathingCam() } hasDied = false; vessel = FlightGlobals.ActiveVessel; - cameraUp = -FlightGlobals.getGeeForceAtPosition(vessel.GetWorldPos3D()).normalized; + 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); + SetCameraParent(vessel.transform); // Use the active vessel's transform without CoM centering as the reference for the parent transform. cameraToolActive = true; } @@ -2210,6 +2200,25 @@ 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 @@ -2247,12 +2256,12 @@ public void VesselCameraShake(Vessel vessel) float atmosphericFactor = (float)vessel.dynamicPressurekPa / 2f; - float angleToCam = Vector3.Angle(vessel.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; @@ -2586,6 +2595,12 @@ void PostDeathRevert(Vessel v) RevertCamera(); } } + + void ResetRoll() + { + stationaryCameraRoll = Quaternion.identity; + flightCamera.transform.rotation = Quaternion.LookRotation(flightCamera.transform.forward, cameraUp); + } #endregion #region GUI @@ -2595,7 +2610,7 @@ void OnGUI() if (guiEnabled && gameUIToggle && HighLogic.LoadedSceneIsFlight) { if (inputFieldStyle == null) SetupInputFieldStyle(); - windowRect = GUI.Window(320, windowRect, GuiWindow, ""); + windowRect = GUI.Window(GUIUtility.GetControlID(FocusType.Passive), windowRect, GuiWindow, ""); if (showKeyframeEditor) { @@ -2605,11 +2620,6 @@ void OnGUI() { PathSelectorWindow(); } - - if (ShowTooltips && !string.IsNullOrEmpty(tooltip)) - { - tooltipRect = GUILayout.Window(GUIUtility.GetControlID(FocusType.Passive), tooltipRect, ShowTooltip, "", GUIStyle.none); - } } if (DEBUG) { @@ -2637,13 +2647,13 @@ void OnGUI() 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 / 2, entryHeight); } + { 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 / 4, contentTop + line * entryHeight, contentWidth / 4, entryHeight); } + { 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) @@ -2665,8 +2675,8 @@ void GuiWindow(int windowID) { GUI.DragWindow(new Rect(0, 0, windowWidth, draggableHeight)); - GUI.Label(new Rect(0, contentTop, windowWidth, 40), Localize("#LOC_CameraTools"), titleStyle); - GUI.Label(new Rect(windowWidth / 2f, contentTop + 35f, windowWidth / 2f - leftIndent - entryHeight, entryHeight), $"{Localize("#LOC_CameraTools_Version")}: {Version}", watermarkStyle); + 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) @@ -2689,23 +2699,22 @@ void GuiWindow(int windowID) float parseResult; //tool mode switcher - GUI.Label(LabelRect(++line), $"{Localize("#LOC_CameraTools_Tool")}: {toolMode}", leftLabelBold); - if (GUI.Button(new Rect(leftIndent, contentTop + (++line * entryHeight), 25, entryHeight - 2), LocTip("#LOC_CameraTools_PrevMode"))) + 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), LocTip("#LOC_CameraTools_NextMode"))) + 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("#LOC_CameraTools_ShowTooltips"), ShowTooltips ? GUI.skin.box : GUI.skin.button)) + 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 (!ShowTooltips) tooltip = ""; } - if (GUI.Button(new Rect(windowWidth - leftIndent - 25, contentTop + (line * entryHeight), 25, entryHeight - 2), LocTip("#LOC_CameraTools_ToggleTextFields"), textInput ? GUI.skin.box : GUI.skin.button)) + 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. @@ -2751,19 +2760,162 @@ void GuiWindow(int windowID) } if (BDArmory.hasBDA) bdArmory.ToggleInputFields(textInput); } - line++; - useAudioEffects = GUI.Toggle(LabelRect(++line), useAudioEffects, LocTip("#LOC_CameraTools_AudioEffects")); - if (enableVFX != (enableVFX = GUI.Toggle(LabelRect(++line), enableVFX, LocTip("#LOC_CameraTools_VisualEffects")))) + ++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, LocTip("#LOC_CameraTools_AutoEnableForBDA")); + 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; + } + + 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) + { + var remainder = 100f - randomModeIVAChance; + var total = randomModeDogfightChance + randomModeStationaryChance + randomModePathingChance; + if (total > 0f) + { + randomModeDogfightChance = Mathf.Round(remainder * randomModeDogfightChance / total); + randomModeStationaryChance = Mathf.Round(remainder * randomModeStationaryChance / total); + randomModePathingChance = Mathf.Round(remainder * randomModePathingChance / total); + } + else + { + 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; + } - line++; + 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 + { + 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) + { + randomModeDogfightChance = Mathf.Round(remainder * randomModeDogfightChance / total); + randomModeIVAChance = Mathf.Round(remainder * randomModeIVAChance / total); + randomModePathingChance = Mathf.Round(remainder * randomModePathingChance / total); + } + else + { + randomModeDogfightChance = Mathf.Round(remainder / 3f); + randomModeIVAChance = Mathf.Round(remainder / 3f); + randomModePathingChance = Mathf.Round(remainder / 3f); + } + 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) + { + randomModeDogfightChance = Mathf.Round(remainder * randomModeDogfightChance / total); + randomModeIVAChance = Mathf.Round(remainder * randomModeIVAChance / total); + randomModeStationaryChance = Mathf.Round(remainder * randomModeStationaryChance / total); + } + else + { + 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; + } + } + + ++line; if (autoFOV && toolMode != ToolModes.Pathing) { - GUI.Label(LeftRect(++line), LocTip("#LOC_CameraTools_AutozoomMargin")); + GUI.Label(LeftRect(++line), Localize("AutozoomMargin")); if (!textInput) { autoZoomMargin = GUI.HorizontalSlider(new Rect(leftIndent, contentTop + ((++line) * entryHeight), contentWidth - 45, entryHeight), autoZoomMargin, 0, autoZoomMarginMax); @@ -2792,11 +2944,10 @@ void GuiWindow(int windowID) } if (toolMode != ToolModes.Pathing) { - autoFOV = GUI.Toggle(LabelRect(++line), autoFOV, "Auto Zoom"); + autoFOV = GUI.Toggle(LabelRect(++line), autoFOV, Localize("Autozoom")); } - ++line; - GUI.Label(LeftRect(++line), "Camera Shake:"); + GUI.Label(LeftRect(++line), Localize("CameraShake")); if (!textInput) { shakeMultiplier = Mathf.Round(GUI.HorizontalSlider(new Rect(leftIndent, contentTop + (++line * entryHeight), contentWidth - 45, entryHeight), shakeMultiplier, 0f, 10f) * 10f) / 10f; @@ -2807,26 +2958,26 @@ void GuiWindow(int windowID) inputFields["shakeMultiplier"].tryParseValue(GUI.TextField(RightRect(line), inputFields["shakeMultiplier"].possibleValue, 8, inputFieldStyle)); shakeMultiplier = inputFields["shakeMultiplier"].currentValue; } - ++line; + ++line; //Stationary camera GUI if (toolMode == ToolModes.StationaryCamera) { - GUI.Label(LeftRect(++line), "Max Relative Vel.: ", leftLabel); + 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, "Maintain Vel."); - if (maintainInitialVelocity) useOrbital = GUI.Toggle(RightRect(line), useOrbital, "Use Orbital"); + 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), "Camera Position:", leftLabel); - string posButtonText = "Set Position w/ Click"; - if (setPresetOffset) posButtonText = "Clear Position"; - if (waitingForPosition) posButtonText = "Waiting..."; + 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) @@ -2839,17 +2990,17 @@ void GuiWindow(int windowID) mouseUp = false; } } - autoFlybyPosition = GUI.Toggle(LabelRect(++line), autoFlybyPosition, "Auto Flyby Position"); - autoLandingPosition = GUI.Toggle(LabelRect(++line), autoLandingPosition, "Auto Landing Position"); ; + 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, "Manual Flyby Position"); + manualOffset = GUI.Toggle(LabelRect(++line), manualOffset, Localize("ManualFlybyPos")); Color origGuiColor = GUI.color; 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), "Fwd:", leftLabel); + 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()); @@ -2860,7 +3011,7 @@ void GuiWindow(int windowID) DrawIncrementButtons(fwdFieldRect, ref manualOffsetForward); guiOffsetForward = manualOffsetForward.ToString(); - GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), 60, entryHeight), "Right:", leftLabel); + GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), 60, entryHeight), Localize("RightPos"), leftLabel); Rect rightFieldRect = new Rect(fwdFieldRect.x, contentTop + (line * entryHeight), textFieldWidth, entryHeight); guiOffsetRight = GUI.TextField(rightFieldRect, guiOffsetRight); if (float.TryParse(guiOffsetRight, out parseResult)) @@ -2870,7 +3021,7 @@ void GuiWindow(int windowID) DrawIncrementButtons(rightFieldRect, ref manualOffsetRight); guiOffsetRight = manualOffsetRight.ToString(); - GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), 60, entryHeight), "Up:", leftLabel); + GUI.Label(new Rect(leftIndent, contentTop + (++line * entryHeight), 60, entryHeight), Localize("UpPos"), leftLabel); Rect upFieldRect = new Rect(fwdFieldRect.x, contentTop + (line * entryHeight), textFieldWidth, entryHeight); guiOffsetUp = GUI.TextField(upFieldRect, guiOffsetUp); if (float.TryParse(guiOffsetUp, out parseResult)) @@ -2878,46 +3029,46 @@ void GuiWindow(int windowID) DrawIncrementButtons(upFieldRect, ref manualOffsetUp); guiOffsetUp = manualOffsetUp.ToString(); GUI.color = origGuiColor; - line++; + ++line; string targetText = "None"; if (camTarget != null) targetText = camTarget.gameObject.name; - GUI.Label(LabelRect(++line), "Camera Target: " + targetText, leftLabel); - string tgtButtonText = "Set Target w/ Click"; - if (waitingForTarget) tgtButtonText = "waiting..."; + GUI.Label(LabelRect(++line), Localize("CameraTarget", targetText), leftLabel); + var tgtButtonText = Localize("SetTargetClick"); + if (waitingForTarget) tgtButtonText = Localize("Waiting"); if (GUI.Button(ThinRect(++line), tgtButtonText)) { waitingForTarget = true; mouseUp = false; } - if (GUI.Button(ThinHalfRect(++line, 0), "Target Self")) + if (GUI.Button(ThinHalfRect(++line, 0), Localize("TargetSelf"))) { camTarget = FlightGlobals.ActiveVessel.GetReferenceTransformPart(); hasTarget = true; } - if (GUI.Button(ThinHalfRect(line, 1), "Clear Target")) + if (GUI.Button(ThinHalfRect(line, 1), Localize("ClearTarget"))) { camTarget = null; hasTarget = false; } - targetCoM = GUI.Toggle(ThinRect(++line), targetCoM, "Vessel Center of Mass"); - if (camTarget == null) saveRotation = GUI.Toggle(ThinRect(++line), saveRotation, "Save Rotation"); - if (!saveRotation) hasSavedRotation = false; + 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) { - GUI.Label(ThinRect(++line), "Secondary Target:"); + GUI.Label(LabelRect(++line), Localize("SecondaryTarget")); string tVesselLabel; if (MouseAimFlight.IsMouseAimActive) { tVesselLabel = "MouseAimFlight"; } else if (showingVesselList) - { tVesselLabel = "Clear"; } + { tVesselLabel = LocalizeStr("Clear"); } else if (BDArmory.hasBDA && bdArmory.useCentroid) - { tVesselLabel = "Centroid"; } + { tVesselLabel = LocalizeStr("Centroid"); } else if (dogfightTarget) { tVesselLabel = dogfightTarget.vesselName; } else - { tVesselLabel = "None"; } + { tVesselLabel = LocalizeStr("None"); } if (GUI.Button(LabelRect(++line), tVesselLabel)) { if (showingVesselList) @@ -2948,9 +3099,9 @@ void GuiWindow(int windowID) { if (bdArmory.hasBDAI) { - if (bdArmory.useBDAutoTarget != (bdArmory.useBDAutoTarget = GUI.Toggle(ThinRect(++line), bdArmory.useBDAutoTarget, "BDA AI Auto Target")) && bdArmory.useBDAutoTarget) + if (bdArmory.useBDAutoTarget != (bdArmory.useBDAutoTarget = GUI.Toggle(ThinRect(++line), bdArmory.useBDAutoTarget, Localize("BDAAutoTarget"))) && bdArmory.useBDAutoTarget) { bdArmory.useCentroid = false; } - GUI.Label(SliderLabelLeft(++line, 110f), "Minimum Interval:"); + GUI.Label(SliderLabelLeft(++line, 110f), Localize("MinimumInterval")); if (!textInput) { bdArmory.AItargetMinimumUpdateInterval = MathUtils.RoundToUnit(GUI.HorizontalSlider(SliderRect(line, 110f), bdArmory.AItargetMinimumUpdateInterval, 0.5f, 5f), 0.5f); @@ -2961,15 +3112,15 @@ void GuiWindow(int windowID) bdArmory.inputFields["AItargetMinimumUpdateInterval"].tryParseValue(GUI.TextField(RightRect(line), bdArmory.inputFields["AItargetMinimumUpdateInterval"].possibleValue, 8, inputFieldStyle)); bdArmory.AItargetMinimumUpdateInterval = bdArmory.inputFields["AItargetMinimumUpdateInterval"].currentValue; } - bdArmory.autoTargetIncomingMissiles = GUI.Toggle(ThinRect(++line), bdArmory.autoTargetIncomingMissiles, "Target Incoming Missiles"); + bdArmory.autoTargetIncomingMissiles = GUI.Toggle(ThinRect(++line), bdArmory.autoTargetIncomingMissiles, Localize("TargetIncomingMissiles")); } - if (bdArmory.useCentroid != (bdArmory.useCentroid = GUI.Toggle(ThinRect(++line), bdArmory.useCentroid, "Target Dogfight Centroid")) && bdArmory.useCentroid) + if (bdArmory.useCentroid != (bdArmory.useCentroid = GUI.Toggle(ThinRect(++line), bdArmory.useCentroid, Localize("TargetDogfightCentroid"))) && bdArmory.useCentroid) { bdArmory.useBDAutoTarget = false; } } ++line; - GUI.Label(SliderLabelLeft(++line, 55f), $"Distance:"); + GUI.Label(SliderLabelLeft(++line, 55f), Localize("Distance")); if (!textInput) { dogfightDistance = GUI.HorizontalSlider(SliderRect(++line, 0f, -8f), dogfightDistance, 1f, dogfightMaxDistance); @@ -2982,44 +3133,44 @@ void GuiWindow(int windowID) dogfightDistance = inputFields["dogfightDistance"].currentValue; } - GUI.Label(LeftRect(++line), "Offset:"); + GUI.Label(LeftRect(++line), Localize("Offset")); if (!textInput) { - GUI.Label(SliderLabelLeft(++line, 15f), "X: "); + GUI.Label(SliderLabelLeft(++line, 15f), Localize("X")); dogfightOffsetX = GUI.HorizontalSlider(SliderRect(line, 15f), dogfightOffsetX, -dogfightMaxOffset, dogfightMaxOffset); if (!enableKeypad) dogfightOffsetX = MathUtils.RoundToUnit(dogfightOffsetX, 1f); GUI.Label(SliderLabelRight(line), $"{dogfightOffsetX:G3}m"); - GUI.Label(SliderLabelLeft(++line, 15f), "Y: "); + GUI.Label(SliderLabelLeft(++line, 15f), Localize("Y")); dogfightOffsetY = GUI.HorizontalSlider(SliderRect(line, 15f), dogfightOffsetY, -dogfightMaxOffset, dogfightMaxOffset); if (!enableKeypad) dogfightOffsetY = MathUtils.RoundToUnit(dogfightOffsetY, 1f); GUI.Label(SliderLabelRight(line), $"{dogfightOffsetY:G3}m"); line += 0.5f; - GUI.Label(SliderLabelLeft(++line, 30f), "Lerp: "); + 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), "Roll: "); + 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 { - GUI.Label(QuarterRect(++line, 0), "X: ", rightLabel); + 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), "Y: ", rightLabel); + 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), "Lerp: ", rightLabel); + 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), "Roll: ", rightLabel); + 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; } - GUI.Label(SliderLabelLeft(++line, 95f), $"Free-Look Thr.:"); + GUI.Label(SliderLabelLeft(++line, 95f), Localize("FreeLookThr")); if (!textInput) { freeLookThresholdSqr = MathUtils.RoundToUnit(GUI.HorizontalSlider(SliderRect(line, 95f), freeLookThresholdSqr, 0f, 1f), 0.1f); @@ -3031,7 +3182,7 @@ void GuiWindow(int windowID) freeLookThresholdSqr = inputFields["freeLookThresholdSqr"].currentValue; } - GUI.Label(SliderLabelLeft(++line, 95f), $"Camera Inertia:"); + GUI.Label(SliderLabelLeft(++line, 95f), Localize("CameraInertia")); if (!textInput) { dogfightInertialFactor = MathUtils.RoundToUnit(GUI.HorizontalSlider(SliderRect(line, 95f), dogfightInertialFactor, 0f, 0.1f), 0.01f); @@ -3043,24 +3194,24 @@ void GuiWindow(int windowID) dogfightInertialFactor = inputFields["dogfightInertialFactor"].currentValue; } - if (dogfightInertialChaseMode != (dogfightInertialChaseMode = GUI.Toggle(LabelRect(++line), dogfightInertialChaseMode, "Inertial Chase Mode"))) + if (dogfightInertialChaseMode != (dogfightInertialChaseMode = GUI.Toggle(LabelRect(++line), dogfightInertialChaseMode, Localize("InertialChaseMode")))) { StartDogfightCamera(); } } else if (toolMode == ToolModes.Pathing) { if (selectedPathIndex >= 0) { - GUI.Label(LabelRect(++line), "Path:"); + GUI.Label(LabelRect(++line), Localize("Path")); currentPath.pathName = GUI.TextField(new Rect(leftIndent + 34, contentTop + (line * entryHeight), contentWidth - 34, entryHeight), currentPath.pathName); } else - { GUI.Label(LabelRect(++line), "Path: None"); } + { GUI.Label(LabelRect(++line), Localize("NoPath")); } line += 0.25f; - if (GUI.Button(LabelRect(++line), "Open Path")) + if (GUI.Button(LabelRect(++line), Localize("OpenPath"))) { TogglePathList(); } - if (GUI.Button(HalfRect(++line, 0), "New Path")) + if (GUI.Button(HalfRect(++line, 0), Localize("NewPath"))) { CreateNewPath(); } - if (GUI.Button(HalfRect(line, 1), "Delete Path")) + if (GUI.Button(HalfRect(line, 1), Localize("DeletePath"))) { DeletePath(selectedPathIndex); } line += 0.25f; @@ -3068,30 +3219,30 @@ void GuiWindow(int windowID) { if (!textInput) { - GUI.Label(LabelRect(++line), "Secondary Smoothing: " + currentPath.secondarySmoothing.ToString("G2")); + 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), "Secondary Smoothing:"); + 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), "Path Timescale: " + currentPath.timeScale.ToString("G3")); + 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), "Path Timescale:"); + 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(LabelRect(++line), useRealTime ? "Real-time" : "In-Game time")) + if (GUI.Button(LabelRect(++line), useRealTime ? Localize("RealTime") : Localize("InGameTime"))) { useRealTime = !useRealTime; } @@ -3129,161 +3280,17 @@ void GuiWindow(int windowID) } GUI.EndScrollView(); line += 5.25f; - if (GUI.Button(new Rect(leftIndent, contentTop + (++line * entryHeight), 3f * contentWidth / 4f, entryHeight), "New Key")) - { CreateNewKeyframe(); } - } - } - line += 0.25f; - - randomMode = GUI.Toggle(LabelRect(++line), randomMode, "Random Mode"); - 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; - } - - 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) - { - var remainder = 100f - randomModeIVAChance; - var total = randomModeDogfightChance + randomModeStationaryChance + randomModePathingChance; - if (total > 0f) - { - randomModeDogfightChance = Mathf.Round(remainder * randomModeDogfightChance / total); - randomModeStationaryChance = Mathf.Round(remainder * randomModeStationaryChance / total); - randomModePathingChance = Mathf.Round(remainder * randomModePathingChance / total); - } - else - { - 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 - { - 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) - { - randomModeDogfightChance = Mathf.Round(remainder * randomModeDogfightChance / total); - randomModeIVAChance = Mathf.Round(remainder * randomModeIVAChance / total); - randomModePathingChance = Mathf.Round(remainder * randomModePathingChance / total); - } - else - { - randomModeDogfightChance = Mathf.Round(remainder / 3f); - randomModeIVAChance = Mathf.Round(remainder / 3f); - randomModePathingChance = Mathf.Round(remainder / 3f); - } - 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) - { - randomModeDogfightChance = Mathf.Round(remainder * randomModeDogfightChance / total); - randomModeIVAChance = Mathf.Round(remainder * randomModeIVAChance / total); - randomModeStationaryChance = Mathf.Round(remainder * randomModeStationaryChance / total); - } - else - { - 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; + 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 += 0.25f; - enableKeypad = GUI.Toggle(LabelRect(++line), enableKeypad, "Keypad Control"); + enableKeypad = GUI.Toggle(ThinRect(++line), enableKeypad, Localize("KeypadControl")); if (enableKeypad) { - GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), "Move Speed:"); + GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), Localize("MoveSpeed")); if (!textInput) { freeMoveSpeedRaw = Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, contentWidth / 2f - 30f), freeMoveSpeedRaw, freeMoveSpeedMinRaw, freeMoveSpeedMaxRaw) * 100f) / 100f; @@ -3296,7 +3303,7 @@ void GuiWindow(int windowID) freeMoveSpeed = inputFields["freeMoveSpeed"].currentValue; } - GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), "Zoom Speed:"); + GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), Localize("ZoomSpeed")); if (!textInput) { zoomSpeedRaw = Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, contentWidth / 2f - 30f), zoomSpeedRaw, zoomSpeedMinRaw, zoomSpeedMaxRaw) * 100f) / 100f; @@ -3309,39 +3316,39 @@ void GuiWindow(int windowID) keyZoomSpeed = inputFields["keyZoomSpeed"].currentValue; } - GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), "Mode:"); + GUI.Label(SliderLabelLeft(++line, contentWidth / 2f - 30f), Localize("ControlMode")); fmMode = (fmModeTypes)Mathf.RoundToInt(GUI.HorizontalSlider(SliderRect(line, contentWidth / 2f - 30f, -30f), (int)fmMode, 0, 1)); GUI.Label(SliderLabelRight(line, 30f), fmMode.ToString()); } - line++; + ++line; // Key bindings - if (GUI.Button(LabelRect(++line), "Edit Keybindings")) + if (GUI.Button(LabelRect(++line), Localize("EditKeybindings"))) { editingKeybindings = !editingKeybindings; } if (editingKeybindings) { - cameraKey = KeyBinding(cameraKey, "Activate", ++line); - revertKey = KeyBinding(revertKey, "Revert", ++line); - toggleMenu = KeyBinding(toggleMenu, "Menu", ++line); - fmUpKey = KeyBinding(fmUpKey, "Up", ++line); - fmDownKey = KeyBinding(fmDownKey, "Down", ++line); - fmForwardKey = KeyBinding(fmForwardKey, "Forward", ++line); - fmBackKey = KeyBinding(fmBackKey, "Back", ++line); - fmLeftKey = KeyBinding(fmLeftKey, "Left", ++line); - fmRightKey = KeyBinding(fmRightKey, "Right", ++line); - fmZoomInKey = KeyBinding(fmZoomInKey, "Zoom In", ++line); - fmZoomOutKey = KeyBinding(fmZoomOutKey, "Zoom Out", ++line); - fmMovementModifier = KeyBinding(fmMovementModifier, "Modifier", ++line); - fmModeToggleKey = KeyBinding(fmModeToggleKey, "FM Mode", ++line); - resetRollKey = KeyBinding(resetRollKey, "Reset Roll", ++line); + 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); + resetRollKey = KeyBinding(resetRollKey, LocalizeStr("ResetRoll"), ++line); } Rect saveRect = HalfRect(++line, 0); - if (GUI.Button(saveRect, "Save")) + if (GUI.Button(saveRect, Localize("Save"))) { Save(); } Rect loadRect = HalfRect(line, 1); - if (GUI.Button(loadRect, "Reload")) + if (GUI.Button(loadRect, Localize("Reload"))) { if (isPlayingPath) StopPlayingPath(); Load(); @@ -3351,7 +3358,7 @@ void GuiWindow(int windowID) if (timeSinceLastSaved < 1) { ++line; - GUI.Label(LabelRect(++line), timeSinceLastSaved < 0.5 ? "Saving..." : "Saved.", centerLabel); + GUI.Label(LabelRect(++line), timeSinceLastSaved < 0.5 ? LocalizeStr("Saving") : LocalizeStr("Saved"), centerLabel); } //fix length @@ -3359,24 +3366,7 @@ void GuiWindow(int windowID) windowRect.height = windowHeight;// = new Rect(windowRect.x, windowRect.y, windowWidth, windowHeight); // Tooltips - if (ShowTooltips && Event.current.type == EventType.Repaint && tooltip != GUI.tooltip) - { - tooltip = GUI.tooltip; // Store the tooltip so we can show it externally to the window. - new GUIContent(GUI.tooltip); - tooltipRect.position = Event.current.mousePosition + windowRect.position; - var lines = tooltip.Split('\n'); - tooltipRect.width = lines.Max(l => GUI.skin.box.CalcSize(new GUIContent(l)).x); - tooltipRect.height = entryHeight * lines.Length; - tooltipRect.x = Mathf.Clamp(tooltipRect.x - tooltipRect.width - 20, 5, Screen.width - tooltipRect.width - 5); - tooltipRect.y = Mathf.Clamp(tooltipRect.y - tooltipRect.height - 10, 5, Screen.height - tooltipRect.height - 5); - } - } - - void ShowTooltip(int windowID) - { - GUILayout.BeginVertical(); - GUILayout.Label(tooltip, GUI.skin.box); - GUILayout.EndVertical(); + if (ShowTooltips && Event.current.type == EventType.Repaint && !string.IsNullOrEmpty(GUI.tooltip)) Tooltips.SetTooltip(GUI.tooltip, Event.current.mousePosition + windowRect.position); } string KeyBinding(string current, string label, float line) @@ -3385,7 +3375,7 @@ string KeyBinding(string current, string label, float line) GUI.Label(new Rect(leftIndent + 70, contentTop + (line * entryHeight), 50, entryHeight), current, leftLabel); if (!isRecordingInput || label != currentlyBinding) { - if (GUI.Button(new Rect(leftIndent + 130, contentTop + (line * entryHeight), 95, entryHeight), "Bind Key")) + if (GUI.Button(new Rect(leftIndent + 130, contentTop + (line * entryHeight), 95, entryHeight), Localize("BindKey"))) { mouseUp = false; isRecordingInput = true; @@ -3394,7 +3384,7 @@ string KeyBinding(string current, string label, float line) } else if (mouseUp) { - GUI.Label(new Rect(leftIndent + 140, contentTop + (line * entryHeight), 85, entryHeight), "Press a Key", leftLabel); + GUI.Label(new Rect(leftIndent + 140, contentTop + (line * entryHeight), 85, entryHeight), Localize("PressAKey"), leftLabel); string inputString = CCInputUtils.GetInputString(); if (inputString.Length > 0) @@ -3419,12 +3409,12 @@ void KeyframeEditorWindow() Rect kWindowRect = new Rect(windowRect.x - width, windowRect.y + 365, width, keyframeEditorWindowHeight); GUI.Box(kWindowRect, string.Empty); GUI.BeginGroup(kWindowRect); - GUI.Label(new Rect(gap, gap, 100, lineHeight - gap), "Keyframe #" + currentKeyframeIndex); - if (GUI.Button(new Rect(100 + gap, gap, 200 - 2 * gap, lineHeight), "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(gap, gap + (++line * lineHeight), 80, lineHeight - gap), "Time: "); + 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)) @@ -3433,7 +3423,7 @@ void KeyframeEditorWindow() } if (currentKeyframeIndex > 1) { - if (GUI.Button(new Rect(100 + gap, gap + (++line * lineHeight), 200 - 2 * gap, lineHeight - gap), "Maintain Speed")) + 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); @@ -3447,18 +3437,18 @@ void KeyframeEditorWindow() currKeyTimeString = currentKeyframeTime.ToString(); } } - GUI.Label(new Rect(gap, gap + (++line * lineHeight), 100, lineHeight - gap), $"Pos: {currentKeyframePositionInterpolationType}"); + 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), $"Rot: {currentKeyframeRotationInterpolationType}"); + 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 + gap, gap + (++line * lineHeight), 200 - 2 * gap, lineHeight - gap), "Apply")) + if (GUI.Button(new Rect(100 + gap, gap + (++line * lineHeight), 200 - 2 * gap, lineHeight - gap), Localize("Apply"))) { 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 + gap, gap + (++line * lineHeight), 200 - 2 * gap, lineHeight - gap), "Cancel")) + if (GUI.Button(new Rect(100 + gap, gap + (++line * lineHeight), 200 - 2 * gap, lineHeight - gap), Localize("Cancel"))) { applied = true; } @@ -3469,6 +3459,9 @@ void KeyframeEditorWindow() 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; @@ -3496,7 +3489,7 @@ void PathSelectorWindow() 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; @@ -3510,6 +3503,9 @@ void PathSelectorWindow() { 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) @@ -3624,7 +3620,7 @@ void CurrentVesselWillDestroy(Vessel v) { if (dogfightTarget != null) { - var distanceSqr = (vessel.transform.position - dogfightTarget.transform.position).sqrMagnitude - flightCamera.Distance * flightCamera.Distance / 4f; + 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()); @@ -3747,8 +3743,9 @@ public void SetZoomImmediate(float zoom) void SetCameraParent(Transform referenceTransform, bool resetToCoM = false) { 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}"); - cameraParent.transform.position = referenceTransform.position; - cameraParent.transform.rotation = referenceTransform.rotation; + 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; @@ -3756,7 +3753,7 @@ void SetCameraParent(Transform referenceTransform, bool resetToCoM = false) if (resetToCoM) { cameraParent.transform.position = vessel.CoM; // Then adjust the flightCamera for the new parent. - flightCamera.transform.localPosition = cameraParent.transform.InverseTransformPoint(referenceTransform.position); + flightCamera.transform.localPosition = cameraParent.transform.InverseTransformPoint(position); flightCamera.transform.localRotation = Quaternion.identity; } origParent.position = enableVFX ? cameraParent.transform.position : FlightGlobals.currentMainBody.position; diff --git a/CameraTools/CameraTools.csproj b/CameraTools/CameraTools.csproj index 6324d775..69a72edd 100644 --- a/CameraTools/CameraTools.csproj +++ b/CameraTools/CameraTools.csproj @@ -72,6 +72,7 @@ + @@ -132,8 +133,11 @@ + + + @@ -141,10 +145,6 @@ - - - -