From e81a3ca8177fa6fbbeb1dc53db8db9f6d7df9223 Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Sat, 7 Feb 2026 23:02:32 -0800 Subject: [PATCH 01/17] Turret Axis Manager TODO: Get all yaw offsets to agree and eliminate the unneeded sliders --- BDArmory/BDArmory.csproj | 1 + BDArmory/WeaponMounts/MissileTurret.cs | 29 ++- BDArmory/WeaponMounts/ModuleTurret.cs | 160 +++++++++---- BDArmory/WeaponMounts/TurretAxisManager.cs | 226 +++++++++++++++++++ BDArmory/Weapons/Missiles/MissileLauncher.cs | 4 + BDArmory/Weapons/ModuleWeapon.cs | 13 +- 6 files changed, 389 insertions(+), 44 deletions(-) create mode 100644 BDArmory/WeaponMounts/TurretAxisManager.cs diff --git a/BDArmory/BDArmory.csproj b/BDArmory/BDArmory.csproj index eee6e22a3..4853b3752 100644 --- a/BDArmory/BDArmory.csproj +++ b/BDArmory/BDArmory.csproj @@ -344,6 +344,7 @@ + diff --git a/BDArmory/WeaponMounts/MissileTurret.cs b/BDArmory/WeaponMounts/MissileTurret.cs index b0c8747f3..2f40ba157 100644 --- a/BDArmory/WeaponMounts/MissileTurret.cs +++ b/BDArmory/WeaponMounts/MissileTurret.cs @@ -112,7 +112,7 @@ public bool isDeployed() //special [KSPField] public bool activeMissileOnly = false; - MissileFire WeaponManager + public MissileFire WeaponManager { get { @@ -279,7 +279,7 @@ IEnumerator ReturnRoutine() bool pitch = !turretEnabled || (deployBlocksPitch && deployBlocksReload); bool yaw = !turretEnabled || (deployBlocksYaw && deployBlocksReload); - while (turret != null && !turret.ReturnTurret(pitch, yaw)) + while (turret != null && !turret.ReturnTurret(pitch, yaw, isReloading)) { UpdateMissilePositions(); yield return new WaitForFixedUpdate(); @@ -321,6 +321,7 @@ public override void OnStart(StartState state) if (tur.Current == null) continue; if (tur.Current.turretID != turretID) continue; turret = tur.Current; + turret.turretMissile = this; break; } tur.Dispose(); @@ -469,12 +470,32 @@ public void SetSlavedGuard(bool slavedGuard, MissileBase ml) } } + public bool IsCurrentWMMissile() + { + MissileFire wm; + if (!(wm = WeaponManager)) return false; + + if (!wm.CurrentMissile) return false; + + return slavedGuard || wm.CurrentMissile.GetPartName() == activeMissile.GetPartName(); + } + public void SlavedAim() { if (pausingAfterShot) return; bool deployCond = hasDeployAnimation && (deployAnimState.normalizedTime < 1 || isReloading); - turret.AimToTarget(slavedTargetPosition, !(deployCond && deployBlocksPitch), !(deployCond && deployBlocksYaw)); + turret.AimToTarget(slavedTargetPosition, !(deployCond && deployBlocksPitch), !(deployCond && deployBlocksYaw), IsCurrentWMMissile()); + } + + public void SetReloadBlock(float duration) + { + // Deploy must block reload and a deploy animation must exist before we block the turret + if (!(deployBlocksReload && hasDeployAnimation)) return; + + // We only block if the respective toggle is also enabled + if (deployBlocksPitch && turret.pitchAxisManager) turret.pitchAxisManager.SetTurretBlock(duration); + if (deployBlocksYaw && turret.yawAxisManager) turret.yawAxisManager.SetTurretBlock(duration); } const int mouseAimLayerMask = (int)(LayerMasks.Parts | LayerMasks.Scenery | LayerMasks.EVA | LayerMasks.Unknown19 | LayerMasks.Unknown23 | LayerMasks.Wheels); @@ -520,7 +541,7 @@ void MouseAim() } bool deployCond = hasDeployAnimation && (deployAnimState.normalizedTime < 1 || isReloading); - turret.AimToTarget(targetPosition, !(deployCond && deployBlocksPitch), !(deployCond && deployBlocksYaw)); + turret.AimToTarget(targetPosition, !(deployCond && deployBlocksPitch), !(deployCond && deployBlocksYaw), IsCurrentWMMissile()); } public void UpdateMissileChildren() diff --git a/BDArmory/WeaponMounts/ModuleTurret.cs b/BDArmory/WeaponMounts/ModuleTurret.cs index ee475812c..397cae39a 100644 --- a/BDArmory/WeaponMounts/ModuleTurret.cs +++ b/BDArmory/WeaponMounts/ModuleTurret.cs @@ -5,6 +5,7 @@ using BDArmory.Settings; using BDArmory.UI; using BDArmory.Utils; +using BDArmory.Weapons; namespace BDArmory.WeaponMounts { @@ -24,6 +25,14 @@ public class ModuleTurret : PartModule public Transform referenceTransform { get; } Transform _referenceTransform; //set this to gun's fireTransform + public ModuleWeapon turretWeapon = null; + public MissileTurret turretMissile = null; + + public TurretAxisManager yawAxisManager = null; + public TurretAxisManager pitchAxisManager = null; + + [KSPField] public int turretPriority = 0; + [KSPField] public float pitchSpeedDPS; [KSPField] public float yawSpeedDPS; @@ -70,6 +79,55 @@ public override void OnStart(StartState state) { base.OnStart(state); + SetupTransforms(); + + SetupTweakables(); + + if (yawTransform) + { + yawAxisManager = new TurretAxisManager(); + yawAxisManager.AddTurrets(part, true, yawTransform); + } + + if (pitchTransform) + { + pitchAxisManager = new TurretAxisManager(); + pitchAxisManager.AddTurrets(part, true, pitchTransform); + } + + if (!string.IsNullOrEmpty(audioPath) && (yawSpeedDPS != 0 || pitchSpeedDPS != 0)) + { + soundClip = SoundUtils.GetAudioClip(audioPath); + + audioSource = gameObject.AddComponent(); + audioSource.clip = soundClip; + audioSource.loop = true; + audioSource.dopplerLevel = 0; + audioSource.minDistance = .5f; + audioSource.maxDistance = 150; + audioSource.Play(); + audioSource.volume = 0; + audioSource.pitch = 0; + audioSource.priority = 9999; + audioSource.spatialBlend = 1; + + if (pitchTransform || yawTransform) + { + lastTurretDirection = baseTransform.InverseTransformDirection(pitchTransform ? pitchTransform.forward : yawTransform.forward); + } + + maxAudioRotRate = Mathf.Min(yawSpeedDPS, pitchSpeedDPS); + + hasAudio = true; + } + } + + bool transformsSetup = false; + + public void SetupTransforms() + { + if (transformsSetup) return; + pitchTransform = part.FindModelTransform(pitchTransformName); yawTransform = part.FindModelTransform(yawTransformName); if (!string.IsNullOrEmpty(baseTransformName)) @@ -123,33 +181,7 @@ public override void OnStart(StartState state) } } - SetupTweakables(); - - if (!string.IsNullOrEmpty(audioPath) && (yawSpeedDPS != 0 || pitchSpeedDPS != 0)) - { - soundClip = SoundUtils.GetAudioClip(audioPath); - - audioSource = gameObject.AddComponent(); - audioSource.clip = soundClip; - audioSource.loop = true; - audioSource.dopplerLevel = 0; - audioSource.minDistance = .5f; - audioSource.maxDistance = 150; - audioSource.Play(); - audioSource.volume = 0; - audioSource.pitch = 0; - audioSource.priority = 9999; - audioSource.spatialBlend = 1; - - if (pitchTransform || yawTransform) - { - lastTurretDirection = baseTransform.InverseTransformDirection(pitchTransform ? pitchTransform.forward : yawTransform.forward); - } - - maxAudioRotRate = Mathf.Min(yawSpeedDPS, pitchSpeedDPS); - - hasAudio = true; - } + transformsSetup = true; } void FixedUpdate() @@ -210,14 +242,24 @@ void Update() void OnDestroy() { GameEvents.onEditorPartPlaced.Remove(OnEditorPartPlaced); + + if (yawAxisManager) + { + Destroy(yawAxisManager); + } + + if (pitchAxisManager) + { + Destroy(pitchAxisManager); + } } - public void AimToTarget(Vector3 targetPosition, bool pitch = true, bool yaw = true) + public void AimToTarget(Vector3 targetPosition, bool pitch = true, bool yaw = true, bool forced = false) { - AimInDirection(targetPosition - _referenceTransform.position, pitch, yaw); + AimInDirection(targetPosition - _referenceTransform.position, pitch, yaw, forced); } - public void AimInDirection(Vector3 targetDirection, bool pitch = true, bool yaw = true) + public void AimInDirection(Vector3 targetDirection, bool pitch = true, bool yaw = true, bool forced = false) { if (!(pitch || yaw)) return; @@ -229,7 +271,8 @@ public void AimInDirection(Vector3 targetDirection, bool pitch = true, bool yaw Vector3 yawComponent; Vector3 pitchComponent; - if (yawTransform) + // Perform the yaw axis manager check here, as we can skip all the calculations if false + if (yawTransform && (!yawAxisManager || yawAxisManager.CheckTurret(this, false, forced))) { yawNormal = yawTransform.up; yawComponent = targetDirection.ProjectOnPlanePreNormalized(yawNormal); @@ -270,7 +313,9 @@ public void AimInDirection(Vector3 targetDirection, bool pitch = true, bool yaw float pitchOffset; float targetPitchAngle; - if (pitchTransform) + + // Perform the pitch axis manager check here, as we can skip all the calculations if false + if (pitchTransform && (!pitchAxisManager || pitchAxisManager.CheckTurret(this, false, forced))) { float pitchError = (float)Vector3d.Angle(pitchComponent, yawNormal) - (float)Vector3d.Angle(_referenceTransform.forward, yawNormal); float currentPitch = -pitchTransform.localEulerAngles.x.ToAngle(); // from current rotation transform @@ -300,7 +345,6 @@ public void AimInDirection(Vector3 targetDirection, bool pitch = true, bool yaw pitchSpeed = pitchSpeedDPS * deltaTime; } - if (yaw) { float linYawMult = pitch && pitchOffset > 0 ? Mathf.Clamp01((yawOffset / pitchOffset) * (pitchSpeedDPS / yawSpeedDPS)) : 1; @@ -316,7 +360,7 @@ public void AimInDirection(Vector3 targetDirection, bool pitch = true, bool yaw public float Pitch => -pitchTransform.localEulerAngles.x.ToAngle(); public float Yaw => yawTransform.localEulerAngles.y.ToAngle(); - public bool ReturnTurret(bool pitch = true, bool yaw = true) + public bool ReturnTurret(bool pitch = true, bool yaw = true, bool reloading = false) { if (!(pitch || yaw)) return true; @@ -324,26 +368,36 @@ public bool ReturnTurret(bool pitch = true, bool yaw = true) float yawOffset; - if (yawTransform) + // If we're yawing, there's a yawTransform, and there's no axis manager or the axis manager allows the movement... + if (yaw && yawTransform && (!yawAxisManager || yawAxisManager.CheckTurret(this, !reloading))) { yawOffset = Quaternion.Angle(yawTransform.localRotation, standbyLocalRotation); } else { yawOffset = 0; - yaw = false; + // Only in the case where we have no yawTransform should yaw flip from true to false + // If !yaw -> yaw should remain false + // If axis manager check fails, yaw should remain true so that we check if the yaw + // axis has returned to the home position + yaw &= yawTransform; } float pitchOffset; - - if (pitchTransform) + + // If we're pitching, there's a pitchTransform, and there's no axis manager or the axis manager allows the movement... + if (pitch && pitchTransform && (!pitchAxisManager || pitchAxisManager.CheckTurret(this, !reloading))) { pitchOffset = VectorUtils.Angle(pitchTransform.forward, yawTransform ? yawTransform.forward : baseTransform.forward); } else { pitchOffset = 0; - pitch = false; + // Only in the case where we have no pitchTransform should pitch flip from true to false + // If !pitch -> pitch should remain false + // If axis manager check fails, pitch should remain true so that we check if the pitch + // axis has returned to the home position + pitch &= pitchTransform; } if (!(pitch || yaw)) return true; @@ -488,6 +542,34 @@ void SetStandbyAngle() standbyLocalRotation = Quaternion.AngleAxis(yawStandbyAngle, Vector3.up); if (yawTransform != null) yawTransform.localRotation = standbyLocalRotation; } + + public bool turretEnabled() + { + if (turretWeapon) + { + switch (turretWeapon.weaponState) + { + case ModuleWeapon.WeaponStates.Enabled: + case ModuleWeapon.WeaponStates.PoweringUp: + case ModuleWeapon.WeaponStates.Locked: + case ModuleWeapon.WeaponStates.EnabledForSecondaryFiring: + { + return true; + } + default: + { + return false; + } + } + } + + if (turretMissile) + { + return turretMissile.turretEnabled; + } + + return false; + } } public class BDAScaleByDistance : PartModule { diff --git a/BDArmory/WeaponMounts/TurretAxisManager.cs b/BDArmory/WeaponMounts/TurretAxisManager.cs new file mode 100644 index 000000000..74213efda --- /dev/null +++ b/BDArmory/WeaponMounts/TurretAxisManager.cs @@ -0,0 +1,226 @@ +using BDArmory.Control; +using BDArmory.Weapons; +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace BDArmory.WeaponMounts +{ + public class TurretAxisManager : MonoBehaviour + { + // Set when any command issued (Aim / Return), no command may be issued until Time.time > timeOfLastMoveCommand + // this is used both for regular movements, to prevent two turrets from making commands in the same frame as + // well as to lock the turret during some action, E.G. deployment of something, or missile turret reloading etc. + public float timeOfLastMoveCommand = 0; + + // Current index of the turret in command + public int currTurretIndex = -1; + + // Sorted list of turrets, based on priority + public List turrets; + + bool _sortingTurretList = false; + + IEnumerator SortTurretListRoutine() + { + _sortingTurretList = true; + + yield return new WaitForFixedUpdate(); + + turrets.Sort(delegate (ModuleTurret t1, ModuleTurret t2) + { + // We want them sorted from greatest to least + return t2.turretPriority.CompareTo(t1.turretPriority); + }); + + _sortingTurretList = false; + } + + void SortTurretList() + { + if (!_sortingTurretList) + { + StartCoroutine(SortTurretListRoutine()); + } + } + + public void AddTurrets(Part part, bool yaw, Transform axisTransform) + { + using (List.Enumerator turr = part.FindModulesImplementing().GetEnumerator()) + while (turr.MoveNext()) + { + if (turr.Current == null) continue; + if (turrets.Contains(turr.Current)) continue; + + turr.Current.SetupTransforms(); + + if ((yaw ? turr.Current.yawTransform : turr.Current.pitchTransform) == axisTransform) + { + turrets.Add(turr.Current); + + if (yaw) + { + turr.Current.yawAxisManager = this; + } + else + { + turr.Current.pitchAxisManager = this; + } + } + } + + // If there's no turrets somehow, or there's only one turret and there's no need to manage it, delete this + if (turrets.Count <= 1) + { + Destroy(this); + } + + SortTurretList(); + } + + // While the commanding weapon can get replaced when timeOfLastMoveCommand > Time.fixedTime + // the turret can only move when timeOfLastMoveCommand < Time.fixedTime + bool CanMove() + { + if (timeOfLastMoveCommand > Time.fixedTime) + { + return false; + } + + timeOfLastMoveCommand = Time.fixedTime; + + return true; + } + + public bool CheckTurret(ModuleTurret t, bool returning, bool forced = false) + { + // Special case, where we've previously confirmed all turrets were disabled + if (currTurretIndex < 0) + { + // If not returning, set currTurretIndex + // NOTE: returning does NOT include missile reload Return()! + if (!returning) + { + currTurretIndex = turrets.IndexOf(t); + } + + // And return CanMove() + return CanMove(); + } + + ModuleTurret currTurret = turrets[currTurretIndex]; + + // If we want to return the turret... + if (returning) + { + // If the current turret is enabled return false + if (currTurret && currTurret.turretEnabled()) + { + return false; + } + + // Check all turrets to see if any are enabled... + for (int i = 0; i < turrets.Count; i++) + { + if (turrets[i].turretEnabled()) + { + currTurretIndex = i; + return false; + } + } + + // If none are, set currTurretIndex to -1 and return CanMove() + currTurretIndex = -1; + return CanMove(); + } + + // If we're forcing the movement, E.G. weapon is current weapon, or we're slavedGuard + if (forced) + { + currTurretIndex = turrets.IndexOf(t); + return CanMove(); + } + + // If the current turret is the turret trying to move, and it's not returning, allow it to do so + if (currTurret == t) + { + return CanMove(); + } + + int startIndex; + + // If currTurret exists... + if (currTurret) + { + // If the current turret's priority is > t's + if (currTurret.turretPriority > t.turretPriority) + { + // If it's enabled, return false + if (currTurret.turretEnabled()) + { + return false; + } + + // Otherwise, we have to check if there's any higher priority turrets enabled, + // starting from currTurretIndex as currTurret comes before t in the list + startIndex = currTurretIndex; + } + else + { + // If the priority of the current turret is lower than t's, then we must check + // the list starting from the highest priority turret, which doubles as a + // IndexOf operation + startIndex = 0; + } + + // Note that we do the above checks first, instead of after these override checks, + // as the priority -> enabled return path is faster than checking what's below + + // No overriding current weapon / missile or slavedGuard + if ((currTurret.turretWeapon && currTurret.turretWeapon.IsCurrentWMWeapon()) || + (currTurret.turretMissile && currTurret.turretMissile.IsCurrentWMMissile())) + { + return false; + } + } + else + { + startIndex = 0; + } + + for (int i = startIndex; i < turrets.Count; i++) + { + if (turrets[i] == null) continue; + + // If the highest priority turret currently enabled is + // our own turret, set currTurretIndex to it, and return CanMove() + // This is essentially like calling IndexOf + if (turrets[i] == t) + { + currTurretIndex = i; + return CanMove(); + } + + // Otherwise, if there's a higher priority turret currently + // enabled, set currTurretIndex to it, and return false + if (turrets[i].turretEnabled()) + { + currTurretIndex = i; + return false; + } + } + + // This *shouldn't* happen but if somehow everything breaks, + // set currTurretIndex to t's index and return CanMove() + currTurretIndex = turrets.IndexOf(t); + return CanMove(); + } + + // Block axis from moving, E.G. for missile turret reloading + public void SetTurretBlock(float duration) + { + timeOfLastMoveCommand = Time.fixedTime + duration; + } + } +} diff --git a/BDArmory/Weapons/Missiles/MissileLauncher.cs b/BDArmory/Weapons/Missiles/MissileLauncher.cs index 201730697..01eb8c757 100644 --- a/BDArmory/Weapons/Missiles/MissileLauncher.cs +++ b/BDArmory/Weapons/Missiles/MissileLauncher.cs @@ -1950,6 +1950,10 @@ public IEnumerator MissileReload() { if (vessel.isActiveVessel) gauge.UpdateReloadMeter(reloadTimer); reloadInProgress = true; + if (turret) + { + turret.SetReloadBlock(reloadableRail.reloadTime); + } yield return new WaitForSecondsFixed(reloadableRail.reloadTime); reloadInProgress = false; launched = false; diff --git a/BDArmory/Weapons/ModuleWeapon.cs b/BDArmory/Weapons/ModuleWeapon.cs index d65441b9b..079f9ec3f 100644 --- a/BDArmory/Weapons/ModuleWeapon.cs +++ b/BDArmory/Weapons/ModuleWeapon.cs @@ -1673,6 +1673,7 @@ public void Start() if (turr.Current.turretID != turretID) continue; turret = turr.Current; turret.SetReferenceTransform(fireTransforms[0]); + turret.turretWeapon = this; break; } if (yawRange == 0 && maxPitch == minPitch) @@ -2066,6 +2067,16 @@ public void ToggleDeploy() } } + public bool IsCurrentWMWeapon() + { + MissileFire wm; + if (!(wm = WeaponManager)) return false; + + if (wm.selectedWeapon == null) return false; + + return GetShortName() == wm.selectedWeapon.GetShortName(); + } + void FAOCos(BaseField field, object obj) { maxAutoFireCosAngle = Mathf.Cos((FiringTolerance * Mathf.Deg2Rad)); @@ -4496,7 +4507,7 @@ void Aim() { turret.smoothRotation = false; } - turret.AimToTarget(finalAimTarget); //no aimbot turrets when target out of sight + turret.AimToTarget(finalAimTarget, forced: IsCurrentWMWeapon()); //no aimbot turrets when target out of sight turret.smoothRotation = origSmooth; } for (int i = 0; i < customTurret.Count; i++) From 75a6a7c7f331d0a90dfa7f49d8dec0667d09512a Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Sun, 8 Feb 2026 10:32:59 -0800 Subject: [PATCH 02/17] Further Work on TurretAxisManager - Deal with reload blocks - Deal with deploy --- BDArmory/WeaponMounts/MissileTurret.cs | 42 +++++- BDArmory/WeaponMounts/ModuleTurret.cs | 36 ++++- BDArmory/WeaponMounts/TurretAxisManager.cs | 147 ++++++++++++++++----- BDArmory/Weapons/ModuleWeapon.cs | 32 ++++- 4 files changed, 213 insertions(+), 44 deletions(-) diff --git a/BDArmory/WeaponMounts/MissileTurret.cs b/BDArmory/WeaponMounts/MissileTurret.cs index 2f40ba157..9c60bd9d3 100644 --- a/BDArmory/WeaponMounts/MissileTurret.cs +++ b/BDArmory/WeaponMounts/MissileTurret.cs @@ -94,6 +94,7 @@ public bool slavedGuard [KSPField] public bool deployBlocksYaw = false; // Turret must deploy before yawing, turret must return to yaw standby position to stow/"undeploy". [KSPField] public bool deployBlocksPitch = false; // Turret must deploy before pitching, turret must return to pitch standby position to stow/"undeploy". public bool isReloading = false; + float _reloadUntil = 0; [KSPField] public bool startsDeployed = false; //Turret starts in deployed position and only uses deploy anim for relaoding. TODO: proper reload anim support for turrets independent of deployAnim //animation @@ -123,6 +124,35 @@ public MissileFire WeaponManager } MissileFire _weaponManager; + public float DeployIfBlocking(bool yaw) + { + // If not blocking, return 0 without doing anything + if (!(yaw ? deployBlocksYaw : deployBlocksPitch)) return 0; + + bool reloadBlock = deployBlocksReload && isReloading; + + // If no deploy animation or deployed and not reloading + if (!(hasDeployAnimation && (deployAnimState.normalizedTime < 1 || reloadBlock))) return 0; + + // If not blocked by reload + if (!reloadBlock) + { + if (deployAnimRoutine != null) + { + StopCoroutine(deployAnimRoutine); + } + + deployAnimRoutine = StartCoroutine(DeployAnimation(true)); + + return deployAnimState.length - deployAnimState.time; + } + else + { + // If blocked by reload, return time that reload completes at + return _reloadUntil - Time.fixedDeltaTime; + } + } + IEnumerator DeployAnimation(bool forward) { var wait = new WaitForFixedUpdate(); @@ -153,6 +183,8 @@ IEnumerator DeployAnimation(bool forward) deployAnimState.normalizedTime = 0; } + turret.SetDeployFlag(!deployBlocksYaw || forward, !deployBlocksPitch || forward); + deployAnimState.speed = 0; } @@ -483,7 +515,7 @@ public bool IsCurrentWMMissile() public void SlavedAim() { if (pausingAfterShot) return; - bool deployCond = hasDeployAnimation && (deployAnimState.normalizedTime < 1 || isReloading); + bool deployCond = hasDeployAnimation && (deployAnimState.normalizedTime < 1 || (deployBlocksReload && isReloading)); turret.AimToTarget(slavedTargetPosition, !(deployCond && deployBlocksPitch), !(deployCond && deployBlocksYaw), IsCurrentWMMissile()); } @@ -493,9 +525,11 @@ public void SetReloadBlock(float duration) // Deploy must block reload and a deploy animation must exist before we block the turret if (!(deployBlocksReload && hasDeployAnimation)) return; + _reloadUntil = Time.fixedDeltaTime + duration; + // We only block if the respective toggle is also enabled - if (deployBlocksPitch && turret.pitchAxisManager) turret.pitchAxisManager.SetTurretBlock(duration); - if (deployBlocksYaw && turret.yawAxisManager) turret.yawAxisManager.SetTurretBlock(duration); + if (deployBlocksPitch && turret.pitchAxisManager) turret.pitchAxisManager.SetTurretBlock(_reloadUntil); + if (deployBlocksYaw && turret.yawAxisManager) turret.yawAxisManager.SetTurretBlock(_reloadUntil); } const int mouseAimLayerMask = (int)(LayerMasks.Parts | LayerMasks.Scenery | LayerMasks.EVA | LayerMasks.Unknown19 | LayerMasks.Unknown23 | LayerMasks.Wheels); @@ -540,7 +574,7 @@ void MouseAim() FlightCamera.fetch.mainCamera.transform.position; } - bool deployCond = hasDeployAnimation && (deployAnimState.normalizedTime < 1 || isReloading); + bool deployCond = hasDeployAnimation && (deployAnimState.normalizedTime < 1 || (deployBlocksReload && isReloading)); turret.AimToTarget(targetPosition, !(deployCond && deployBlocksPitch), !(deployCond && deployBlocksYaw), IsCurrentWMMissile()); } diff --git a/BDArmory/WeaponMounts/ModuleTurret.cs b/BDArmory/WeaponMounts/ModuleTurret.cs index 397cae39a..c68fb4df0 100644 --- a/BDArmory/WeaponMounts/ModuleTurret.cs +++ b/BDArmory/WeaponMounts/ModuleTurret.cs @@ -29,7 +29,9 @@ public class ModuleTurret : PartModule public MissileTurret turretMissile = null; public TurretAxisManager yawAxisManager = null; + public int yawAxisIndex = 0; public TurretAxisManager pitchAxisManager = null; + public int pitchAxisIndex = 0; [KSPField] public int turretPriority = 0; @@ -85,13 +87,13 @@ public override void OnStart(StartState state) if (yawTransform) { - yawAxisManager = new TurretAxisManager(); + yawAxisManager = part.gameObject.AddComponent(); yawAxisManager.AddTurrets(part, true, yawTransform); } if (pitchTransform) { - pitchAxisManager = new TurretAxisManager(); + pitchAxisManager = part.gameObject.AddComponent(); pitchAxisManager.AddTurrets(part, true, pitchTransform); } @@ -254,12 +256,12 @@ void OnDestroy() } } - public void AimToTarget(Vector3 targetPosition, bool pitch = true, bool yaw = true, bool forced = false) + public void AimToTarget(Vector3 targetPosition, bool pitch = true, bool yaw = true, bool activeWeap = false) { - AimInDirection(targetPosition - _referenceTransform.position, pitch, yaw, forced); + AimInDirection(targetPosition - _referenceTransform.position, pitch, yaw, activeWeap); } - public void AimInDirection(Vector3 targetDirection, bool pitch = true, bool yaw = true, bool forced = false) + public void AimInDirection(Vector3 targetDirection, bool pitch = true, bool yaw = true, bool activeWeap = false) { if (!(pitch || yaw)) return; @@ -272,7 +274,7 @@ public void AimInDirection(Vector3 targetDirection, bool pitch = true, bool yaw Vector3 pitchComponent; // Perform the yaw axis manager check here, as we can skip all the calculations if false - if (yawTransform && (!yawAxisManager || yawAxisManager.CheckTurret(this, false, forced))) + if (yawTransform && (!yawAxisManager || yawAxisManager.CheckTurret(this, false, activeWeap))) { yawNormal = yawTransform.up; yawComponent = targetDirection.ProjectOnPlanePreNormalized(yawNormal); @@ -315,7 +317,7 @@ public void AimInDirection(Vector3 targetDirection, bool pitch = true, bool yaw float targetPitchAngle; // Perform the pitch axis manager check here, as we can skip all the calculations if false - if (pitchTransform && (!pitchAxisManager || pitchAxisManager.CheckTurret(this, false, forced))) + if (pitchTransform && (!pitchAxisManager || pitchAxisManager.CheckTurret(this, false, activeWeap))) { float pitchError = (float)Vector3d.Angle(pitchComponent, yawNormal) - (float)Vector3d.Angle(_referenceTransform.forward, yawNormal); float currentPitch = -pitchTransform.localEulerAngles.x.ToAngle(); // from current rotation transform @@ -543,6 +545,26 @@ void SetStandbyAngle() if (yawTransform != null) yawTransform.localRotation = standbyLocalRotation; } + public float DeployIfBlocking(bool yaw) + { + if (turretWeapon) + { + return turretWeapon.DeployIfBlocking(); + } + if (turretMissile) + { + return turretMissile.DeployIfBlocking(yaw); + } + + return 0; + } + + public void SetDeployFlag(bool yaw, bool pitch) + { + if (yawAxisManager) yawAxisManager.SetTurretFlag(!yaw, yawAxisIndex); + if (pitchAxisManager) pitchAxisManager.SetTurretFlag(!pitch, pitchAxisIndex); + } + public bool turretEnabled() { if (turretWeapon) diff --git a/BDArmory/WeaponMounts/TurretAxisManager.cs b/BDArmory/WeaponMounts/TurretAxisManager.cs index 74213efda..5a07d9c83 100644 --- a/BDArmory/WeaponMounts/TurretAxisManager.cs +++ b/BDArmory/WeaponMounts/TurretAxisManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Runtime.CompilerServices; using UnityEngine; namespace BDArmory.WeaponMounts @@ -22,6 +23,10 @@ public class TurretAxisManager : MonoBehaviour bool _sortingTurretList = false; + bool _yaw = false; + + int _blockedTurrets = 0; // Bitmask used for determining which turrets are blocked + IEnumerator SortTurretListRoutine() { _sortingTurretList = true; @@ -34,6 +39,18 @@ IEnumerator SortTurretListRoutine() return t2.turretPriority.CompareTo(t1.turretPriority); }); + for (int i = 0; i < turrets.Count; i++) + { + if (_yaw) + { + turrets[i].yawAxisIndex = i; + } + else + { + turrets[i].pitchAxisIndex = i; + } + } + _sortingTurretList = false; } @@ -47,28 +64,38 @@ void SortTurretList() public void AddTurrets(Part part, bool yaw, Transform axisTransform) { - using (List.Enumerator turr = part.FindModulesImplementing().GetEnumerator()) - while (turr.MoveNext()) - { - if (turr.Current == null) continue; - if (turrets.Contains(turr.Current)) continue; + _yaw = yaw; + List turr = part.FindModulesImplementing(); + ModuleTurret currTurret; - turr.Current.SetupTransforms(); + // Pre-allocate list + turrets = new List(turrets.Count); + _blockedTurrets = 0; - if ((yaw ? turr.Current.yawTransform : turr.Current.pitchTransform) == axisTransform) + for (int i = 0; i < turr.Count; i++) + { + if ((currTurret = turr[i]) == null) continue; + if (turrets.Contains(currTurret)) continue; + + currTurret.SetupTransforms(); + + if ((_yaw ? currTurret.yawTransform : currTurret.pitchTransform) == axisTransform) + { + turrets.Add(currTurret); + + if (_yaw) + { + currTurret.yawAxisManager = this; + } + else { - turrets.Add(turr.Current); - - if (yaw) - { - turr.Current.yawAxisManager = this; - } - else - { - turr.Current.pitchAxisManager = this; - } + currTurret.pitchAxisManager = this; } + + // Default the turret to blocked, resolve it later when checking + _blockedTurrets |= 1 << i; } + } // If there's no turrets somehow, or there's only one turret and there's no need to manage it, delete this if (turrets.Count <= 1) @@ -81,6 +108,7 @@ public void AddTurrets(Part part, bool yaw, Transform axisTransform) // While the commanding weapon can get replaced when timeOfLastMoveCommand > Time.fixedTime // the turret can only move when timeOfLastMoveCommand < Time.fixedTime + [MethodImpl(MethodImplOptions.AggressiveInlining)] bool CanMove() { if (timeOfLastMoveCommand > Time.fixedTime) @@ -88,12 +116,28 @@ bool CanMove() return false; } + if (_blockedTurrets != 0) + { + float currBlockedTime = 0; + for (int i = 0; i < turrets.Count; i++) + { + if ((_blockedTurrets & (1 << i)) != 0) + { + float tempTime = turrets[i].DeployIfBlocking(_yaw); + if (tempTime > currBlockedTime) currBlockedTime = tempTime; + } + } + + if (currBlockedTime > timeOfLastMoveCommand) timeOfLastMoveCommand = currBlockedTime; + return false; + } + timeOfLastMoveCommand = Time.fixedTime; return true; } - public bool CheckTurret(ModuleTurret t, bool returning, bool forced = false) + public bool CheckTurret(ModuleTurret t, bool returning, bool activeWeap = false) { // Special case, where we've previously confirmed all turrets were disabled if (currTurretIndex < 0) @@ -102,7 +146,7 @@ public bool CheckTurret(ModuleTurret t, bool returning, bool forced = false) // NOTE: returning does NOT include missile reload Return()! if (!returning) { - currTurretIndex = turrets.IndexOf(t); + currTurretIndex = GetTurretIndex(t); } // And return CanMove() @@ -135,16 +179,25 @@ public bool CheckTurret(ModuleTurret t, bool returning, bool forced = false) return CanMove(); } - // If we're forcing the movement, E.G. weapon is current weapon, or we're slavedGuard - if (forced) + // If the current turret is the turret trying to move, and it's not returning, allow it to do so + if (currTurret == t) { - currTurretIndex = turrets.IndexOf(t); return CanMove(); } - // If the current turret is the turret trying to move, and it's not returning, allow it to do so - if (currTurret == t) + // If we're forcing the movement, E.G. we're the current weapon or we're in slavedGuard + if (activeWeap) { + // If there's a turret, check if it has higher priority, and if it is also part of the + // current weapon set, if so, then prioritize that + if (currTurret && currTurret.turretPriority > t.turretPriority && + ((currTurret.turretWeapon && currTurret.turretWeapon.IsCurrentWMWeapon()) || + (currTurret.turretMissile && currTurret.turretMissile.IsCurrentWMMissile()))) + { + return false; + } + + currTurretIndex = GetTurretIndex(t); return CanMove(); } @@ -169,8 +222,11 @@ public bool CheckTurret(ModuleTurret t, bool returning, bool forced = false) else { // If the priority of the current turret is lower than t's, then we must check - // the list starting from the highest priority turret, which doubles as a - // IndexOf operation + // the list starting from the highest priority turret. While this is technically + // less efficient in the case where t is the highest priority turret active, and + // there are higher priority turrets than t that are not active, this does prevent + // repeated replacements of currTurretIndex and incorrectly prioritized turret + // movements startIndex = 0; } @@ -178,8 +234,7 @@ public bool CheckTurret(ModuleTurret t, bool returning, bool forced = false) // as the priority -> enabled return path is faster than checking what's below // No overriding current weapon / missile or slavedGuard - if ((currTurret.turretWeapon && currTurret.turretWeapon.IsCurrentWMWeapon()) || - (currTurret.turretMissile && currTurret.turretMissile.IsCurrentWMMissile())) + if (IsCurrentWMTurr(currTurret)) { return false; } @@ -195,7 +250,6 @@ public bool CheckTurret(ModuleTurret t, bool returning, bool forced = false) // If the highest priority turret currently enabled is // our own turret, set currTurretIndex to it, and return CanMove() - // This is essentially like calling IndexOf if (turrets[i] == t) { currTurretIndex = i; @@ -213,14 +267,43 @@ public bool CheckTurret(ModuleTurret t, bool returning, bool forced = false) // This *shouldn't* happen but if somehow everything breaks, // set currTurretIndex to t's index and return CanMove() - currTurretIndex = turrets.IndexOf(t); + currTurretIndex = GetTurretIndex(t); return CanMove(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetTurretIndex(ModuleTurret t) + { + return _yaw ? t.yawAxisIndex : t.pitchAxisIndex; + } + // Block axis from moving, E.G. for missile turret reloading - public void SetTurretBlock(float duration) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetTurretBlock(float time) { - timeOfLastMoveCommand = Time.fixedTime + duration; + if (timeOfLastMoveCommand < time) + { + timeOfLastMoveCommand = time; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsCurrentWMTurr(ModuleTurret t) + { + return (t.turretWeapon && t.turretWeapon.IsCurrentWMWeapon()) || (t.turretMissile && t.turretMissile.IsCurrentWMMissile()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetTurretFlag(bool blocked, int index) + { + if (blocked) + { + _blockedTurrets |= 1 << index; + } + else + { + _blockedTurrets &= ~(1 << index); + } } } } diff --git a/BDArmory/Weapons/ModuleWeapon.cs b/BDArmory/Weapons/ModuleWeapon.cs index 079f9ec3f..837d2e816 100644 --- a/BDArmory/Weapons/ModuleWeapon.cs +++ b/BDArmory/Weapons/ModuleWeapon.cs @@ -4507,7 +4507,7 @@ void Aim() { turret.smoothRotation = false; } - turret.AimToTarget(finalAimTarget, forced: IsCurrentWMWeapon()); //no aimbot turrets when target out of sight + turret.AimToTarget(finalAimTarget, activeWeap: IsCurrentWMWeapon()); //no aimbot turrets when target out of sight turret.smoothRotation = origSmooth; } for (int i = 0; i < customTurret.Count; i++) @@ -6265,6 +6265,26 @@ void UpdateGUIWeaponState() guiStatusString = weaponState.ToString(); } + public float DeployIfBlocking() + { + // If no deploy anim or deploy state, the turret is ready regardless + if (!(hasDeployAnim && deployState)) return 0; + + if (hasReloadAnim && isReloading) // If reloading, return how long it'll take for the reload to complete + { + return reloadState.length - reloadState.time; + } + + // If currently deploying + if (deployState.enabled && deployState.speed > 0) return deployState.length - deployState.time; + + // Otherwise, weapon is either shutting down or inactive, thus requiring startup routine + StopShutdownStartupRoutines(); + startupRoutine = StartCoroutine(StartupRoutine(true)); + + return deployState.length; + } + IEnumerator StartupRoutine(bool calledByReload = false, bool secondaryFiring = false) { if (hasReloadAnim && isReloading) //wait for reload to finish before shutting down @@ -6292,6 +6312,12 @@ IEnumerator StartupRoutine(bool calledByReload = false, bool secondaryFiring = f else weaponState = WeaponStates.EnabledForSecondaryFiring; } + + if (turret) + { + turret.SetDeployFlag(true, true); + } + UpdateGUIWeaponState(); UpdateOffsetWeapon(); // Re-calculate offset/non-centerline weapon corrections on weapon selection BDArmorySetup.Instance.UpdateCursorState(); @@ -6356,6 +6382,10 @@ IEnumerator ShutdownRoutine(bool calledByReload = false) weaponState = WeaponStates.Disabled; UpdateGUIWeaponState(); } + if (turret) + { + turret.SetDeployFlag(false, false); + } } IEnumerator ReloadRoutine() { From af0567a5bebc3c7f3b505ce12ce6637b1dfa297d Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Sat, 14 Feb 2026 16:38:12 -0800 Subject: [PATCH 03/17] More SendTargetDataToMissile Fixes - Fix laser missile PD behavior - Set targetVessel to guardTarget if null in the None and Default -> Bomb cases --- BDArmory/Control/MissileFire.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/BDArmory/Control/MissileFire.cs b/BDArmory/Control/MissileFire.cs index 678290952..a4b60ebbf 100644 --- a/BDArmory/Control/MissileFire.cs +++ b/BDArmory/Control/MissileFire.cs @@ -8463,7 +8463,7 @@ public void SendTargetDataToMissile(MissileBase ml, Vessel targetVessel, bool cl { ml.lockedCamera = foundCam; ml.TargetAcquired = true; - if (guardMode && guardTarget != null && (foundCam.groundTargetPosition - guardTarget.CoM).sqrMagnitude < 10 * 10) validTarget = true; //*highly* unlikely laser-guided missiles used for missile interception, so leaving these guardTarget + if (guardMode && (targetVessel ? targetVessel : (targetVessel = guardTarget)) && (foundCam.groundTargetPosition - targetVessel.CoM).sqrMagnitude < 10 * 10) validTarget = true; } else { @@ -8564,7 +8564,10 @@ public void SendTargetDataToMissile(MissileBase ml, Vessel targetVessel, bool cl } } else + { ml.radarTarget = vesselRadarData.lockedTargetData.targetData; + } + ml.vrd = vesselRadarData; vesselRadarData.LastMissile = ml; @@ -8595,6 +8598,10 @@ public void SendTargetDataToMissile(MissileBase ml, Vessel targetVessel, bool cl case MissileBase.TargetingModes.None: { ml.TargetAcquired = true; + if (guardMode && null == targetVessel) + { + targetVessel = guardTarget; + } validTarget = true; break; } @@ -8703,6 +8710,10 @@ public void SendTargetDataToMissile(MissileBase ml, Vessel targetVessel, bool cl { if (ml.GetWeaponClass() == WeaponClasses.Bomb) { + if (guardMode && null == targetVessel) + { + targetVessel = guardTarget; + } validTarget = true; } break; From 484126d45fb59e49edad421e3820c3f7c3f6fcbe Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Sat, 14 Feb 2026 19:30:32 -0800 Subject: [PATCH 04/17] Remove Factor of 2 for Ping Time --- BDArmory/Weapons/Missiles/MissileBase.cs | 4 ++-- BDArmory/Weapons/Missiles/MissileLauncher.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BDArmory/Weapons/Missiles/MissileBase.cs b/BDArmory/Weapons/Missiles/MissileBase.cs index ab972d461..8570599a8 100644 --- a/BDArmory/Weapons/Missiles/MissileBase.cs +++ b/BDArmory/Weapons/Missiles/MissileBase.cs @@ -1111,7 +1111,7 @@ protected void UpdateRadarTarget() if (scannedTargets == null) scannedTargets = new TargetSignatureData[BDATargetManager.LoadedVessels.Count]; TargetSignatureData.ResetTSDArray(ref scannedTargets); Ray ray = new Ray(transform.position, vectorToTarget); - bool pingRWR = Time.time - lastRWRPing > (2f * RadarUtils.ACTIVE_MISSILE_PING_PERISTS_TIME); + bool pingRWR = Time.time - lastRWRPing > (RadarUtils.ACTIVE_MISSILE_PING_PERISTS_TIME); if (pingRWR) lastRWRPing = Time.time; bool radarSnapshot = (snapshotTicker > 10); if (radarSnapshot) @@ -1227,7 +1227,7 @@ protected void UpdateRadarTarget() TargetSignatureData.ResetTSDArray(ref scannedTargets); Vector3 forward = GetForwardTransform(); Ray ray = new Ray(transform.position, forward); - bool pingRWR = Time.time - lastRWRPing > (2f * RadarUtils.ACTIVE_MISSILE_PING_PERISTS_TIME); + bool pingRWR = Time.time - lastRWRPing > (RadarUtils.ACTIVE_MISSILE_PING_PERISTS_TIME); if (pingRWR) lastRWRPing = Time.time; bool radarSnapshot = (snapshotTicker > 5); if (radarSnapshot) diff --git a/BDArmory/Weapons/Missiles/MissileLauncher.cs b/BDArmory/Weapons/Missiles/MissileLauncher.cs index 01eb8c757..6f5ff3a72 100644 --- a/BDArmory/Weapons/Missiles/MissileLauncher.cs +++ b/BDArmory/Weapons/Missiles/MissileLauncher.cs @@ -2632,7 +2632,7 @@ private void UpdateTerminalGuidance() ActiveRadar = true; updateRadarCS = true; - bool pingRWR = Time.time - lastRWRPing > (2f * RadarUtils.ACTIVE_MISSILE_PING_PERISTS_TIME); + bool pingRWR = Time.time - lastRWRPing > (RadarUtils.ACTIVE_MISSILE_PING_PERISTS_TIME); if (pingRWR) lastRWRPing = Time.time; //RadarUtils.UpdateRadarLock(ray, maxOffBoresight, activeRadarMinThresh, ref scannedTargets, 0.4f, true, RadarWarningReceiver.RWRThreatTypes.MissileLock, true); From 2855a2b889528878b293daae6f3881557398753e Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Sat, 14 Feb 2026 22:27:21 -0800 Subject: [PATCH 05/17] More Features for Axis Manager - Account for yawStandbyAngle - Complete deploy mechanics - Favor remaining on the current turret for equal priority cases (except when the new turret in question is the active turret) - Fix bug in yawStandbyAngle implementation for parts with multiple turrets --- BDArmory/WeaponMounts/MissileTurret.cs | 29 ++++++- BDArmory/WeaponMounts/ModuleTurret.cs | 94 +++++++++++++++------ BDArmory/WeaponMounts/TurretAxisManager.cs | 97 ++++++++++++++++++---- BDArmory/Weapons/ModuleWeapon.cs | 16 ++-- 4 files changed, 183 insertions(+), 53 deletions(-) diff --git a/BDArmory/WeaponMounts/MissileTurret.cs b/BDArmory/WeaponMounts/MissileTurret.cs index 9c60bd9d3..ee4c9905b 100644 --- a/BDArmory/WeaponMounts/MissileTurret.cs +++ b/BDArmory/WeaponMounts/MissileTurret.cs @@ -126,13 +126,30 @@ public MissileFire WeaponManager public float DeployIfBlocking(bool yaw) { + if (!hasDeployAnimation) + { + turret.SetDeployFlag(true, true); + return 0; + } + // If not blocking, return 0 without doing anything - if (!(yaw ? deployBlocksYaw : deployBlocksPitch)) return 0; + if (!(yaw ? deployBlocksYaw : deployBlocksPitch)) + { + if (yaw) + { + turret.SetYawDeployFlag(true); + } + else + { + turret.SetPitchDeployFlag(true); + } + return 0; + } bool reloadBlock = deployBlocksReload && isReloading; // If no deploy animation or deployed and not reloading - if (!(hasDeployAnimation && (deployAnimState.normalizedTime < 1 || reloadBlock))) return 0; + if (!(deployAnimState.normalizedTime < 1 || reloadBlock)) return 0; // If not blocked by reload if (!reloadBlock) @@ -167,6 +184,9 @@ IEnumerator DeployAnimation(bool forward) } deployAnimState.normalizedTime = 1; + + // Unblock turret + turret.SetDeployFlag(true, true); } else { @@ -174,6 +194,9 @@ IEnumerator DeployAnimation(bool forward) yield return new WaitWhileFixed(() => pausingAfterShot); + // Block turret prior to undeploy + turret.SetDeployFlag(!deployBlocksYaw, !deployBlocksPitch); + while (deployAnimState.normalizedTime > 0) { deployAnimState.speed = -deployAnimationSpeed; @@ -183,8 +206,6 @@ IEnumerator DeployAnimation(bool forward) deployAnimState.normalizedTime = 0; } - turret.SetDeployFlag(!deployBlocksYaw || forward, !deployBlocksPitch || forward); - deployAnimState.speed = 0; } diff --git a/BDArmory/WeaponMounts/ModuleTurret.cs b/BDArmory/WeaponMounts/ModuleTurret.cs index c68fb4df0..3309db31f 100644 --- a/BDArmory/WeaponMounts/ModuleTurret.cs +++ b/BDArmory/WeaponMounts/ModuleTurret.cs @@ -6,6 +6,7 @@ using BDArmory.UI; using BDArmory.Utils; using BDArmory.Weapons; +using System.Collections.Generic; namespace BDArmory.WeaponMounts { @@ -54,6 +55,7 @@ public class ModuleTurret : PartModule UI_FloatRange(minValue = -90f, maxValue = 90f, stepIncrement = 0.5f, scene = UI_Scene.All, affectSymCounterparts = UI_Scene.None)] public float yawStandbyAngle = 0; Quaternion standbyLocalRotation;// = Quaternion.identity; + bool _yawStandbyAngleEnabled = true; [KSPField(isPersistant = true)] public float minPitchLimit = 400; [KSPField(isPersistant = true)] public float maxPitchLimit = 400; @@ -85,16 +87,22 @@ public override void OnStart(StartState state) SetupTweakables(); - if (yawTransform) + if (yawTransform && !yawAxisManager) { yawAxisManager = part.gameObject.AddComponent(); - yawAxisManager.AddTurrets(part, true, yawTransform); + if (!yawAxisManager.AddTurrets(part, true, yawTransform)) + { + yawAxisManager = null; + } } - if (pitchTransform) + if (pitchTransform && !pitchAxisManager) { pitchAxisManager = part.gameObject.AddComponent(); - pitchAxisManager.AddTurrets(part, true, pitchTransform); + if (!pitchAxisManager.AddTurrets(part, false, pitchTransform)) + { + pitchAxisManager = null; + } } if (!string.IsNullOrEmpty(audioPath) && (yawSpeedDPS != 0 || pitchSpeedDPS != 0)) @@ -369,40 +377,38 @@ public bool ReturnTurret(bool pitch = true, bool yaw = true, bool reloading = fa float deltaTime = Time.fixedDeltaTime; float yawOffset; + // Are we yawing? Is there a yawTransform? Are we actually yawed? + // The last check is important as it allows us to skip the CheckTurret check, which causes turrets that were stowed to redeploy + bool checkYaw = yaw && yawTransform && !(yawTransform.localRotation == standbyLocalRotation); - // If we're yawing, there's a yawTransform, and there's no axis manager or the axis manager allows the movement... - if (yaw && yawTransform && (!yawAxisManager || yawAxisManager.CheckTurret(this, !reloading))) + // If we're yawing, there's a yawTransform, we're yawed, and there's no axis manager or the axis manager allows the movement... + if (checkYaw && (!yawAxisManager || yawAxisManager.CheckTurret(this, !reloading))) { yawOffset = Quaternion.Angle(yawTransform.localRotation, standbyLocalRotation); } else { yawOffset = 0; - // Only in the case where we have no yawTransform should yaw flip from true to false - // If !yaw -> yaw should remain false - // If axis manager check fails, yaw should remain true so that we check if the yaw - // axis has returned to the home position - yaw &= yawTransform; + yaw = false; } float pitchOffset; + // Are we pitching? Is there a pitchTransform? Are we actually pitched? + // The last check is important as it allows us to skip the CheckTurret check, which causes turrets that were stowed to redeploy + bool checkPitch = pitch && pitchTransform && !(pitchTransform.localRotation == Quaternion.identity); - // If we're pitching, there's a pitchTransform, and there's no axis manager or the axis manager allows the movement... - if (pitch && pitchTransform && (!pitchAxisManager || pitchAxisManager.CheckTurret(this, !reloading))) + // If we're pitching, there's a pitchTransform, we're pitched, and there's no axis manager or the axis manager allows the movement... + if (checkPitch && (!pitchAxisManager || pitchAxisManager.CheckTurret(this, !reloading))) { pitchOffset = VectorUtils.Angle(pitchTransform.forward, yawTransform ? yawTransform.forward : baseTransform.forward); } else { pitchOffset = 0; - // Only in the case where we have no pitchTransform should pitch flip from true to false - // If !pitch -> pitch should remain false - // If axis manager check fails, pitch should remain true so that we check if the pitch - // axis has returned to the home position - pitch &= pitchTransform; + pitch = false; } - if (!(pitch || yaw)) return true; + if (!(checkPitch || checkYaw)) return true; float yawSpeed; float pitchSpeed; @@ -429,7 +435,7 @@ public bool ReturnTurret(bool pitch = true, bool yaw = true, bool reloading = fa pitchTransform.localRotation = Quaternion.RotateTowards(pitchTransform.localRotation, Quaternion.identity, pitchSpeed * linPitchMult); } - return (!yaw || yawTransform.localRotation == standbyLocalRotation) && (!pitch || pitchTransform.localRotation == Quaternion.identity); + return (!checkYaw || yawTransform.localRotation == standbyLocalRotation) && (!checkPitch || pitchTransform.localRotation == Quaternion.identity); } public bool TargetInRange(Vector3 targetPosition, float maxDistance, float thresholdDegrees = 0) @@ -523,9 +529,20 @@ void SetupStandbyLocalRotation(BaseField field = null, object obj = null) void OnStandbyAngleChanged(BaseField field = null, object obj = null) { SetStandbyAngle(); + PropagateStandbyAngle(); foreach (Part symmetryPart in part.symmetryCounterparts) { - ModuleTurret symmetryTurret = symmetryPart.FindModuleImplementing(); + ModuleTurret symmetryTurret = null; + + List turrets = symmetryPart.FindModulesImplementing(); + for (int i = 0; i < turrets.Count; i++) + { + if (turrets[i] == null) continue; + if (turrets[i].turretID == turretID) symmetryTurret = turrets[i]; + } + + if (!symmetryTurret) continue; + if (part.symMethod == SymmetryMethod.Mirror) { symmetryTurret.yawStandbyAngle = -yawStandbyAngle; @@ -536,13 +553,28 @@ void OnStandbyAngleChanged(BaseField field = null, object obj = null) } symmetryTurret.SetStandbyAngle(); + symmetryTurret.PropagateStandbyAngle(); + } + } + + void PropagateStandbyAngle() + { + if (yawAxisManager) + { + yawAxisManager.SetYawStandbyAngle(this, yawStandbyAngle); } } - void SetStandbyAngle() + public void SetStandbyAngle() { standbyLocalRotation = Quaternion.AngleAxis(yawStandbyAngle, Vector3.up); - if (yawTransform != null) yawTransform.localRotation = standbyLocalRotation; + if (yawTransform != null && _yawStandbyAngleEnabled) yawTransform.localRotation = standbyLocalRotation; + } + + public void DisableYawStandbyAngle() + { + Fields["yawStandbyAngle"].guiActiveEditor = false; + _yawStandbyAngleEnabled = false; } public float DeployIfBlocking(bool yaw) @@ -559,10 +591,20 @@ public float DeployIfBlocking(bool yaw) return 0; } - public void SetDeployFlag(bool yaw, bool pitch) + public void SetDeployFlag(bool yawEnabled, bool pitchEnabled) + { + if (yawAxisManager) yawAxisManager.SetTurretFlag(!yawEnabled, yawAxisIndex); + if (pitchAxisManager) pitchAxisManager.SetTurretFlag(!pitchEnabled, pitchAxisIndex); + } + + public void SetYawDeployFlag(bool yawEnabled) + { + if (yawAxisManager) yawAxisManager.SetTurretFlag(!yawEnabled, yawAxisIndex); + } + + public void SetPitchDeployFlag(bool pitchEnabled) { - if (yawAxisManager) yawAxisManager.SetTurretFlag(!yaw, yawAxisIndex); - if (pitchAxisManager) pitchAxisManager.SetTurretFlag(!pitch, pitchAxisIndex); + if (pitchAxisManager) pitchAxisManager.SetTurretFlag(!pitchEnabled, pitchAxisIndex); } public bool turretEnabled() diff --git a/BDArmory/WeaponMounts/TurretAxisManager.cs b/BDArmory/WeaponMounts/TurretAxisManager.cs index 5a07d9c83..4ecff799f 100644 --- a/BDArmory/WeaponMounts/TurretAxisManager.cs +++ b/BDArmory/WeaponMounts/TurretAxisManager.cs @@ -36,7 +36,15 @@ IEnumerator SortTurretListRoutine() turrets.Sort(delegate (ModuleTurret t1, ModuleTurret t2) { // We want them sorted from greatest to least - return t2.turretPriority.CompareTo(t1.turretPriority); + int temp = t2.turretPriority.CompareTo(t1.turretPriority); + + if (temp != 0) + { + return temp; + } + + // If equal, sort from lowest turretID to highest + return t1.turretID.CompareTo(t2.turretID); }); for (int i = 0; i < turrets.Count; i++) @@ -44,6 +52,10 @@ IEnumerator SortTurretListRoutine() if (_yaw) { turrets[i].yawAxisIndex = i; + if (i != 0) + { + turrets[i].DisableYawStandbyAngle(); + } } else { @@ -62,14 +74,23 @@ void SortTurretList() } } - public void AddTurrets(Part part, bool yaw, Transform axisTransform) + // Checks for other turrets on this axis, returns true if successful + public bool AddTurrets(Part part, bool yaw, Transform axisTransform) { + // Need to null this in the calling turret + if (axisTransform == null) + { + Destroy(this); + return false; + } + _yaw = yaw; + List turr = part.FindModulesImplementing(); ModuleTurret currTurret; // Pre-allocate list - turrets = new List(turrets.Count); + turrets = new List(turr.Count); _blockedTurrets = 0; for (int i = 0; i < turr.Count; i++) @@ -97,13 +118,28 @@ public void AddTurrets(Part part, bool yaw, Transform axisTransform) } } - // If there's no turrets somehow, or there's only one turret and there's no need to manage it, delete this + // If there's no turrets somehow, or there's only one turret and there's no need to manage this axis, delete this if (turrets.Count <= 1) { Destroy(this); + return false; } SortTurretList(); + + return true; + } + + public void SetYawStandbyAngle(ModuleTurret caller, float standbyAngle) + { + for (int i = 0; i < turrets.Count; i++) + { + if (turrets[i] == null) continue; + if (turrets[i] == caller) continue; + + turrets[i].yawStandbyAngle = standbyAngle; + turrets[i].SetStandbyAngle(); + } } // While the commanding weapon can get replaced when timeOfLastMoveCommand > Time.fixedTime @@ -118,17 +154,7 @@ bool CanMove() if (_blockedTurrets != 0) { - float currBlockedTime = 0; - for (int i = 0; i < turrets.Count; i++) - { - if ((_blockedTurrets & (1 << i)) != 0) - { - float tempTime = turrets[i].DeployIfBlocking(_yaw); - if (tempTime > currBlockedTime) currBlockedTime = tempTime; - } - } - - if (currBlockedTime > timeOfLastMoveCommand) timeOfLastMoveCommand = currBlockedTime; + DeployTurrets(); return false; } @@ -137,6 +163,21 @@ bool CanMove() return true; } + void DeployTurrets() + { + float currBlockedTime = 0; + for (int i = 0; i < turrets.Count; i++) + { + if ((_blockedTurrets & (1 << i)) != 0) + { + float tempTime = turrets[i].DeployIfBlocking(_yaw); + if (tempTime > currBlockedTime) currBlockedTime = tempTime; + } + } + + if (currBlockedTime > timeOfLastMoveCommand) timeOfLastMoveCommand = currBlockedTime; + } + public bool CheckTurret(ModuleTurret t, bool returning, bool activeWeap = false) { // Special case, where we've previously confirmed all turrets were disabled @@ -167,6 +208,7 @@ public bool CheckTurret(ModuleTurret t, bool returning, bool activeWeap = false) // Check all turrets to see if any are enabled... for (int i = 0; i < turrets.Count; i++) { + if (turrets[i] == null) continue; if (turrets[i].turretEnabled()) { currTurretIndex = i; @@ -180,7 +222,7 @@ public bool CheckTurret(ModuleTurret t, bool returning, bool activeWeap = false) } // If the current turret is the turret trying to move, and it's not returning, allow it to do so - if (currTurret == t) + if (currTurret && currTurret == t) { return CanMove(); } @@ -190,7 +232,7 @@ public bool CheckTurret(ModuleTurret t, bool returning, bool activeWeap = false) { // If there's a turret, check if it has higher priority, and if it is also part of the // current weapon set, if so, then prioritize that - if (currTurret && currTurret.turretPriority > t.turretPriority && + if (currTurret && currTurret.turretPriority >= t.turretPriority && ((currTurret.turretWeapon && currTurret.turretWeapon.IsCurrentWMWeapon()) || (currTurret.turretMissile && currTurret.turretMissile.IsCurrentWMMissile()))) { @@ -207,7 +249,7 @@ public bool CheckTurret(ModuleTurret t, bool returning, bool activeWeap = false) if (currTurret) { // If the current turret's priority is > t's - if (currTurret.turretPriority > t.turretPriority) + if (currTurret.turretPriority >= t.turretPriority) { // If it's enabled, return false if (currTurret.turretEnabled()) @@ -271,6 +313,25 @@ public bool CheckTurret(ModuleTurret t, bool returning, bool activeWeap = false) return CanMove(); } + void OnDestroy() + { + if (turrets == null) return; + + for (int i = 0; i < turrets.Count; i++) + { + if (turrets[i] == null) continue; + + if (_yaw) + { + turrets[i].yawAxisManager = null; + } + else + { + turrets[i].pitchAxisManager = null; + } + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetTurretIndex(ModuleTurret t) { diff --git a/BDArmory/Weapons/ModuleWeapon.cs b/BDArmory/Weapons/ModuleWeapon.cs index 837d2e816..5c499f624 100644 --- a/BDArmory/Weapons/ModuleWeapon.cs +++ b/BDArmory/Weapons/ModuleWeapon.cs @@ -6268,7 +6268,11 @@ void UpdateGUIWeaponState() public float DeployIfBlocking() { // If no deploy anim or deploy state, the turret is ready regardless - if (!(hasDeployAnim && deployState)) return 0; + if (!(hasDeployAnim && deployState)) + { + turret.SetDeployFlag(true, true); + return 0; + } if (hasReloadAnim && isReloading) // If reloading, return how long it'll take for the reload to complete { @@ -6370,6 +6374,12 @@ IEnumerator ShutdownRoutine(bool calledByReload = false) } if (hasDeployAnim && deployState != null) { + // Block turret prior to undeploy + if (turret) + { + turret.SetDeployFlag(false, false); + } + deployState.enabled = true; deployState.speed = -1; yield return new WaitWhileFixed(() => deployState.normalizedTime > 0); @@ -6382,10 +6392,6 @@ IEnumerator ShutdownRoutine(bool calledByReload = false) weaponState = WeaponStates.Disabled; UpdateGUIWeaponState(); } - if (turret) - { - turret.SetDeployFlag(false, false); - } } IEnumerator ReloadRoutine() { From ead935cd8e63209b11869dfd8e3399a1156172bf Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Sun, 15 Feb 2026 16:06:00 -0800 Subject: [PATCH 06/17] Set Up Edge Case Handling for Axis Manager - Set up functions to handle stowing turrets in edge case, though for now testing seems to indicate this is unnecessary --- BDArmory/WeaponMounts/MissileTurret.cs | 2 ++ BDArmory/WeaponMounts/ModuleTurret.cs | 12 ++++++++++++ BDArmory/WeaponMounts/TurretAxisManager.cs | 15 +++++++++++++++ BDArmory/Weapons/ModuleWeapon.cs | 10 ++++++++++ 4 files changed, 39 insertions(+) diff --git a/BDArmory/WeaponMounts/MissileTurret.cs b/BDArmory/WeaponMounts/MissileTurret.cs index ee4c9905b..530757db7 100644 --- a/BDArmory/WeaponMounts/MissileTurret.cs +++ b/BDArmory/WeaponMounts/MissileTurret.cs @@ -161,6 +161,8 @@ public float DeployIfBlocking(bool yaw) deployAnimRoutine = StartCoroutine(DeployAnimation(true)); + hasReturned = false; + return deployAnimState.length - deployAnimState.time; } else diff --git a/BDArmory/WeaponMounts/ModuleTurret.cs b/BDArmory/WeaponMounts/ModuleTurret.cs index 3309db31f..9722ad65b 100644 --- a/BDArmory/WeaponMounts/ModuleTurret.cs +++ b/BDArmory/WeaponMounts/ModuleTurret.cs @@ -591,6 +591,18 @@ public float DeployIfBlocking(bool yaw) return 0; } + public void StowTurret() + { + if (turretWeapon) + { + turretWeapon.ReturnWeapon(); + } + if (turretMissile) + { + turretMissile.ReturnTurret(); + } + } + public void SetDeployFlag(bool yawEnabled, bool pitchEnabled) { if (yawAxisManager) yawAxisManager.SetTurretFlag(!yawEnabled, yawAxisIndex); diff --git a/BDArmory/WeaponMounts/TurretAxisManager.cs b/BDArmory/WeaponMounts/TurretAxisManager.cs index 4ecff799f..8e08878f2 100644 --- a/BDArmory/WeaponMounts/TurretAxisManager.cs +++ b/BDArmory/WeaponMounts/TurretAxisManager.cs @@ -178,6 +178,21 @@ void DeployTurrets() if (currBlockedTime > timeOfLastMoveCommand) timeOfLastMoveCommand = currBlockedTime; } + public void StowTurrets() + { + if (currTurretIndex >= 0) + { + return; + } + + for (int i = 0; i < turrets.Count; i++) + { + if (turrets[i] == null) continue; + + turrets[i].StowTurret(); + } + } + public bool CheckTurret(ModuleTurret t, bool returning, bool activeWeap = false) { // Special case, where we've previously confirmed all turrets were disabled diff --git a/BDArmory/Weapons/ModuleWeapon.cs b/BDArmory/Weapons/ModuleWeapon.cs index 5c499f624..c8adcca95 100644 --- a/BDArmory/Weapons/ModuleWeapon.cs +++ b/BDArmory/Weapons/ModuleWeapon.cs @@ -3999,6 +3999,16 @@ public void DisableWeapon() if (part.isActiveAndEnabled) shutdownRoutine = StartCoroutine(ShutdownRoutine()); } + public void ReturnWeapon() + { + if (!disabledStates.Contains(weaponState)) + return; + + StopShutdownStartupRoutines(); + + if (part.isActiveAndEnabled) shutdownRoutine = StartCoroutine(ShutdownRoutine()); + } + HashSet standbyStates = new HashSet { WeaponStates.Standby, WeaponStates.PoweringUp, WeaponStates.Locked }; public void StandbyWeapon() { From 01309db21eb8226ab098c3c1f1d8102d6561b025 Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Sun, 15 Feb 2026 16:54:28 -0800 Subject: [PATCH 07/17] Turret Audio Error Handling - Fix issues where turret audio would error into NaN --- BDArmory/WeaponMounts/ModuleTurret.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/BDArmory/WeaponMounts/ModuleTurret.cs b/BDArmory/WeaponMounts/ModuleTurret.cs index 9722ad65b..4d33d0622 100644 --- a/BDArmory/WeaponMounts/ModuleTurret.cs +++ b/BDArmory/WeaponMounts/ModuleTurret.cs @@ -126,7 +126,21 @@ public override void OnStart(StartState state) lastTurretDirection = baseTransform.InverseTransformDirection(pitchTransform ? pitchTransform.forward : yawTransform.forward); } - maxAudioRotRate = Mathf.Min(yawSpeedDPS, pitchSpeedDPS); + audioRotationRate = 0; + //maxAudioRotRate = Mathf.Min(yawSpeedDPS, pitchSpeedDPS); + maxAudioRotRate = Mathf.Min(yawTransform ? yawSpeedDPS : float.MaxValue, pitchTransform ? pitchSpeedDPS : float.MaxValue); + + // If one of the two values is zero, try to salvage things + if (maxAudioRotRate <= 0) + { + maxAudioRotRate = Mathf.Max(yawTransform ? yawSpeedDPS : -1, pitchTransform ? pitchSpeedDPS : -1); + } + + // If all else fails, default to 90 DPS + if (maxAudioRotRate == float.MaxValue || maxAudioRotRate <= 0) + { + maxAudioRotRate = 90; + } hasAudio = true; } From 4f6cc16905131ff71cca7d945dcc56c7c62fdc85 Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Sun, 15 Feb 2026 23:25:28 -0800 Subject: [PATCH 08/17] Fix MML MissileReferenceTransform Handling - MML MissileReferenceTransform was getting set to transforms that were likely dummies, resulting in incorrect MissileReferenceTransform readings --- BDArmory/Weapons/Missiles/MultiMissileLauncher.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/BDArmory/Weapons/Missiles/MultiMissileLauncher.cs b/BDArmory/Weapons/Missiles/MultiMissileLauncher.cs index 1f93aa424..6d4d3cd72 100644 --- a/BDArmory/Weapons/Missiles/MultiMissileLauncher.cs +++ b/BDArmory/Weapons/Missiles/MultiMissileLauncher.cs @@ -192,11 +192,6 @@ IEnumerator DelayedStart() missileLauncher.reloadableRail = missileSpawner; missileLauncher.hasAmmo = true; missileLauncher.multiLauncher = this; - missileLauncher.MissileReferenceTransform = part.FindModelTransform("missileTransform"); - if (!missileLauncher.MissileReferenceTransform) - { - missileLauncher.MissileReferenceTransform = launchTransforms[0]; - } if (isClusterMissile) { @@ -228,6 +223,7 @@ IEnumerator DelayedStart() { Fields["clusterMissileTriggerDist"].guiActive = false; Fields["clusterMissileTriggerDist"].guiActiveEditor = false; + missileLauncher.MissileReferenceTransform = launchTransforms[0]; } Fields["salvoSize"].guiActive = setSalvoSize; Fields["salvoSize"].guiActiveEditor = setSalvoSize; From b3390a372e496df180e758d77ba355b648af43d4 Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Tue, 17 Feb 2026 16:10:44 -0800 Subject: [PATCH 09/17] Correct Incorrect Usage of ammoCount in MMR - MMR was using ammoCount for GetModuleMass and GetModuleCost resulting in double-counting the mass and cost of missiles in magazines, which already account for this. --- BDArmory/Weapons/Missiles/ModuleMissileRearm.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BDArmory/Weapons/Missiles/ModuleMissileRearm.cs b/BDArmory/Weapons/Missiles/ModuleMissileRearm.cs index e1bb21100..b27d9803a 100644 --- a/BDArmory/Weapons/Missiles/ModuleMissileRearm.cs +++ b/BDArmory/Weapons/Missiles/ModuleMissileRearm.cs @@ -15,10 +15,10 @@ namespace BDArmory.Weapons.Missiles { public class ModuleMissileRearm : PartModule, IPartMassModifier, IPartCostModifier { - public float GetModuleMass(float baseMass, ModifierStagingSituation situation) => Mathf.Max((isMultiLauncher ? ammoCount : ammoCount - 1), 0) * missileMass; + public float GetModuleMass(float baseMass, ModifierStagingSituation situation) => Mathf.Max((isMultiLauncher ? (int)railAmmo : (int)railAmmo - 1), 0) * missileMass; public ModifierChangeWhen GetModuleMassChangeWhen() => ModifierChangeWhen.FIXED; - public float GetModuleCost(float baseCost, ModifierStagingSituation situation) => Mathf.Max((isMultiLauncher ? ammoCount : ammoCount - 1), 0) * missileCost; + public float GetModuleCost(float baseCost, ModifierStagingSituation situation) => Mathf.Max((isMultiLauncher ? (int)railAmmo : (int)railAmmo - 1), 0) * missileCost; public ModifierChangeWhen GetModuleCostChangeWhen() => ModifierChangeWhen.FIXED; private float missileMass = 0; From 3803e834d1c63b2da5cadca836b1f35c5874d702 Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Tue, 17 Feb 2026 16:14:55 -0800 Subject: [PATCH 10/17] Fix Team Icon Viewing Dist = Unlimited - Previously this would be limited to MAX_GUARD_VISUAL_RANGE instead of being unlimited as reported in the menu, resulting in confusion --- BDArmory/UI/BDATeamIcons.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BDArmory/UI/BDATeamIcons.cs b/BDArmory/UI/BDATeamIcons.cs index 9664fe50d..31c654130 100644 --- a/BDArmory/UI/BDATeamIcons.cs +++ b/BDArmory/UI/BDATeamIcons.cs @@ -220,7 +220,7 @@ void UpdateUI() float size = 40; UpdateStyles(); float minDistanceSqr = BDTISettings.DISTANCE_THRESHOLD * BDTISettings.DISTANCE_THRESHOLD; - float maxDistanceSqr = BDTISettings.MAX_DISTANCE_THRESHOLD * BDTISettings.MAX_DISTANCE_THRESHOLD; + float maxDistanceSqr = BDTISettings.MAX_DISTANCE_THRESHOLD >= BDArmorySettings.MAX_GUARD_VISUAL_RANGE ? float.MaxValue : BDTISettings.MAX_DISTANCE_THRESHOLD * BDTISettings.MAX_DISTANCE_THRESHOLD; using var vessel = FlightGlobals.Vessels.GetEnumerator(); while (vessel.MoveNext()) { From 266a9aae307c7b1b41de38067e3b7fb8977ac4c7 Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Tue, 17 Feb 2026 20:14:57 -0800 Subject: [PATCH 11/17] Fix TurretAxisManager Conditional Time check should be >= rather than just > --- BDArmory/WeaponMounts/TurretAxisManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BDArmory/WeaponMounts/TurretAxisManager.cs b/BDArmory/WeaponMounts/TurretAxisManager.cs index 8e08878f2..04b2b58b8 100644 --- a/BDArmory/WeaponMounts/TurretAxisManager.cs +++ b/BDArmory/WeaponMounts/TurretAxisManager.cs @@ -147,7 +147,7 @@ public void SetYawStandbyAngle(ModuleTurret caller, float standbyAngle) [MethodImpl(MethodImplOptions.AggressiveInlining)] bool CanMove() { - if (timeOfLastMoveCommand > Time.fixedTime) + if (timeOfLastMoveCommand >= Time.fixedTime) { return false; } From 668439046162428d2fd45f3b4467a12e9a538487 Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Thu, 19 Feb 2026 17:46:33 -0800 Subject: [PATCH 12/17] Expand RWR MissileLock Array + Reduce Ping Slot Usage - Use 2 * dataCount for MWS, launchWarnings and missileLockData - Reduce missileLockData slot usage by reducing MissileLockLife by fixedDeltaTime --- BDArmory/Radar/RadarWarningReceiver.cs | 8 ++++---- BDArmory/Weapons/Missiles/MissileBase.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/BDArmory/Radar/RadarWarningReceiver.cs b/BDArmory/Radar/RadarWarningReceiver.cs index 2e743ac4d..1a875caf6 100644 --- a/BDArmory/Radar/RadarWarningReceiver.cs +++ b/BDArmory/Radar/RadarWarningReceiver.cs @@ -163,11 +163,11 @@ public override void OnStart(StartState state) if (HighLogic.LoadedSceneIsFlight) { pingsData = new RWRSignatureData[dataCount]; - MWSData = new RWRSignatureData[dataCount]; + MWSData = new RWRSignatureData[2 *dataCount]; //pingWorldPositions = new Vector3[dataCount]; RWRSignatureData.ResetRWRSDArray(ref pingsData); - launchWarnings = new RWRSignatureData[dataCount]; //new List(); - missileLockData = new RWRSignatureData[dataCount]; + launchWarnings = new RWRSignatureData[2 * dataCount]; //new List(); + missileLockData = new RWRSignatureData[2 * dataCount]; rwrIconLabelStyle = new GUIStyle(); rwrIconLabelStyle.alignment = TextAnchor.MiddleCenter; @@ -424,7 +424,7 @@ IEnumerator PingLifeRoutine(int index, float lifeTime) IEnumerator MissileLockLifeRoutine(int index) { - yield return new WaitForSecondsFixed(RadarUtils.ACTIVE_MISSILE_PING_PERISTS_TIME); + yield return new WaitForSecondsFixed(RadarUtils.ACTIVE_MISSILE_PING_PERISTS_TIME - Time.fixedDeltaTime); missileLockData[index] = RWRSignatureData.noTarget; // Data expiring // Decrement size and move the head over diff --git a/BDArmory/Weapons/Missiles/MissileBase.cs b/BDArmory/Weapons/Missiles/MissileBase.cs index 8570599a8..823e35a32 100644 --- a/BDArmory/Weapons/Missiles/MissileBase.cs +++ b/BDArmory/Weapons/Missiles/MissileBase.cs @@ -1111,8 +1111,8 @@ protected void UpdateRadarTarget() if (scannedTargets == null) scannedTargets = new TargetSignatureData[BDATargetManager.LoadedVessels.Count]; TargetSignatureData.ResetTSDArray(ref scannedTargets); Ray ray = new Ray(transform.position, vectorToTarget); - bool pingRWR = Time.time - lastRWRPing > (RadarUtils.ACTIVE_MISSILE_PING_PERISTS_TIME); - if (pingRWR) lastRWRPing = Time.time; + bool pingRWR = Time.fixedTime - lastRWRPing > (RadarUtils.ACTIVE_MISSILE_PING_PERISTS_TIME); + if (pingRWR) lastRWRPing = Time.fixedTime; bool radarSnapshot = (snapshotTicker > 10); if (radarSnapshot) { From 7b74f3398c8feba1818851e5d6696aa3be4c4bad Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Sat, 21 Feb 2026 18:22:32 -0800 Subject: [PATCH 13/17] Better IR PD Implementation - Just use GetHeatTarget skipping the whole SearchForHeatTarget logic, *assume* we can somehow point the missile towards the target --- BDArmory/Control/MissileFire.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/BDArmory/Control/MissileFire.cs b/BDArmory/Control/MissileFire.cs index a4b60ebbf..9376966ae 100644 --- a/BDArmory/Control/MissileFire.cs +++ b/BDArmory/Control/MissileFire.cs @@ -9837,16 +9837,18 @@ public void PointDefenseTurretFiring() } // Look for a better way to do this... - SearchForHeatTarget(currMissile, PDMslTgts[MissileID]); + //SearchForHeatTarget(currMissile, PDMslTgts[MissileID]); + Vector3 missilePos = currMissile.MissileReferenceTransform.position + 5f * currMissile.GetForwardTransform(); + TargetSignatureData currHeatTarget = BDATargetManager.GetHeatTarget(vessel, vessel, new Ray(missilePos, targetVessel.CoM - missilePos), TargetSignatureData.noTarget, 0.5f * currMissile.lockedSensorFOV, currMissile.heatThreshold, currMissile.frontAspectHeatModifier, currMissile.uncagedLock, currMissile.targetCoM, currMissile.lockedSensorFOVBias, currMissile.lockedSensorVelocityBias, currMissile.lockedSensorVelocityMagnitudeBias, currMissile.lockedSensorMinAngularVelocity, this, PDMslTgts[MissileID], IFF: currMissile.hasIFF); // If no heat target -> we're probably out of view - if (!heatTarget.exists) + if (!currHeatTarget.exists) { continue; } // If we get decoyed -> we're gonna need a better missile, so skip this one - if (heatTarget.isDecoy) + if (currHeatTarget.isDecoy) { // Write down the missile type that failed to lock //if (logging) @@ -9857,7 +9859,7 @@ public void PointDefenseTurretFiring() } // If we haven't gotten a heat target, continue - if (heatTarget.vessel != targetVessel) + if (currHeatTarget.vessel != targetVessel) { continue; } @@ -9882,9 +9884,8 @@ public void PointDefenseTurretFiring() //need to see if missile is turreted (and is a unique turret we haven't seen yet); if so, check if target is within traverse, else see if target is within boresight bool turreted = false; MissileTurret mT = null; - if (launcher && (launcher.missileTurret || launcher.multiLauncher && launcher.multiLauncher.turret)) + if (launcher && (mT = launcher.missileTurret ? launcher.missileTurret : launcher.multiLauncher.turret)) { - mT = launcher.missileTurret ? launcher.missileTurret : launcher.multiLauncher.turret; if (!MslTurrets.Contains(mT)) { turreted = true; @@ -9895,7 +9896,7 @@ public void PointDefenseTurretFiring() mT.SlavedAim(); } } - if (currMissile.customTurret.Count> 0) + if (currMissile.customTurret.Count > 0) { for (int i = 0; i < currMissile.customTurret.Count; i++) { From 838ba913142111cb2b7f8262541f721446614484 Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Sat, 21 Feb 2026 22:58:26 -0800 Subject: [PATCH 14/17] Fix Various Deficiencies in PD and GMR - Because PD is separate from standard Guard functionalities, the various "SearchFor" functions don't actually work as intended when GMR is firing a PD missile --- BDArmory/Control/MissileFire.cs | 92 +++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/BDArmory/Control/MissileFire.cs b/BDArmory/Control/MissileFire.cs index 9376966ae..d89938cff 100644 --- a/BDArmory/Control/MissileFire.cs +++ b/BDArmory/Control/MissileFire.cs @@ -2060,17 +2060,26 @@ void UpdateWeaponIndex() void UpdateGuidanceTargets() { UpdateHMDRay(); + CalculateMissilesAway(); + ClearQueuedLaunches(); + + if (guardFiringMissile) + { + SearchForLaserPoint(currFiringMissile); + SearchForHeatTarget(currFiringMissile, currFiringTarget); + SearchForRadarSource(currFiringMissile); + return; + } + if (weaponIndex > 0 && (selectedWeapon.GetWeaponClass() == WeaponClasses.Missile || selectedWeapon.GetWeaponClass() == WeaponClasses.SLW || selectedWeapon.GetWeaponClass() == WeaponClasses.Bomb)) { - SearchForLaserPoint(); + SearchForLaserPoint(CurrentMissile); SearchForHeatTarget(CurrentMissile); - SearchForRadarSource(); + SearchForRadarSource(CurrentMissile); } - CalculateMissilesAway(); - ClearQueuedLaunches(); } public void ToggleHMD() @@ -2773,14 +2782,20 @@ float GetMissileTurretFireAngle(MissileLauncher mlauncher) return (mTurret.turretLoft, mTurret.turretLoftFac); } - IEnumerator GuardMissileRoutine(Vessel targetVessel, MissileBase ml) + MissileBase currFiringMissile = null; + TargetInfo currFiringTarget = null; + + IEnumerator GuardMissileRoutine(TargetInfo tgtInfo, MissileBase ml) { if (ml && !guardFiringMissile) { bool dumbfiring = false; guardFiringMissile = true; + currFiringMissile = ml; + currFiringTarget = tgtInfo; var wait = new WaitForFixedUpdate(); - float tryLockTime = targetVessel.IsMissile() ? 0.05f : 0.25f; // More urgency for incoming missiles + Vessel targetVessel = tgtInfo.Vessel; + float tryLockTime = tgtInfo.isMissile ? 0.05f : 0.25f; // More urgency for incoming missiles switch (ml.TargetingMode) { case MissileBase.TargetingModes.Radar: @@ -3517,6 +3532,8 @@ IEnumerator GuardMissileRoutine(Vessel targetVessel, MissileBase ml) dumbfiring = false; } guardFiringMissile = false; + currFiringTarget = null; + currFiringMissile = null; } } IEnumerator GuardBombRoutine() @@ -7423,7 +7440,7 @@ bool SmartPickWeapon_EngagementEnvelope(TargetInfo target) //targetWeaponPriority = candidatePriority; } } - float fovAngle = VectorUtils.Angle(Missile.GetForwardTransform(), targetVessel.CoM - Missile.transform.position); + float fovAngle = VectorUtils.Angle(Missile.GetForwardTransform(), targetVessel.CoM - Missile.MissileReferenceTransform.position); if (fovAngle > Missile.missileFireAngle && Missile.missileFireAngle < Missile.maxOffBoresight * 0.75f) { candidateYield *= Missile.missileFireAngle / fovAngle; //missile is clamped to a narrow boresight - do we have anything with a wider FoV we should start with? @@ -8205,24 +8222,22 @@ public bool CanSeeTarget(MissileBase target) return false; } - void SearchForRadarSource() + void SearchForRadarSource(MissileBase currMissile) { antiRadTargetAcquired = false; antiRadiationTarget = Vector3.zero; if (rwr && rwr.rwrEnabled) { float closestAngle = 360; - MissileBase missile = CurrentMissile; - - if (!missile) return; + + if (!currMissile) return; + if (currMissile.TargetingMode != MissileBase.TargetingModes.AntiRad) return; - float maxOffBoresight = missile.maxOffBoresight; + float maxOffBoresight = currMissile.maxOffBoresight; - if (missile.TargetingMode != MissileBase.TargetingModes.AntiRad) return; - - MissileLauncher ml = CurrentMissile as MissileLauncher; - Vector3 missilePos = missile.transform.position; - Vector3 missileForward = missile.GetForwardTransform(); + //MissileLauncher ml = currMissile as MissileLauncher; + Vector3 missilePos = currMissile.MissileReferenceTransform.position; + Vector3 missileForward = currMissile.GetForwardTransform(); //Debug.Log($"antiradTgt count: {(ml.antiradTargets != null ? ml.antiradTargets.Length : "null")}"); //if (ml.antiradTargets == null) ml.ParseAntiRadTargetTypes(); for (int i = 0; i < rwr.pingsData.Length; i++) @@ -8230,7 +8245,7 @@ void SearchForRadarSource() RWRSignatureData currPing = rwr.pingsData[i]; // These CanDetectRWRThreat function calls can probably be replaced with the actual code, // but I think this is more readable and maintainable for anyone not familiar with bitmasks - if (currPing.exists && RadarWarningReceiver.CanDetectRWRThreat(ml.antiradTargets, currPing.signalType)) + if (currPing.exists && RadarWarningReceiver.CanDetectRWRThreat(currMissile.antiradTargets, currPing.signalType)) { Vector3 position = currPing.position; float angle = VectorUtils.Angle(position - missilePos, missileForward); @@ -8247,15 +8262,14 @@ void SearchForRadarSource() } } - void SearchForLaserPoint() + void SearchForLaserPoint(MissileBase currMissile) { - MissileBase ml = CurrentMissile; - if (!ml || !(ml.TargetingMode == MissileBase.TargetingModes.Laser || ml.TargetingMode == MissileBase.TargetingModes.Gps)) + if (!currMissile || !(currMissile.TargetingMode == MissileBase.TargetingModes.Laser || currMissile.TargetingMode == MissileBase.TargetingModes.Gps)) { return; } - MissileLauncher launcher = ml as MissileLauncher; + MissileLauncher launcher = currMissile as MissileLauncher; if (launcher != null) { bool parentOnly = launcher.GuidanceMode == GuidanceModes.BeamRiding || launcher.GuidanceMode == GuidanceModes.CLOS || launcher.GuidanceMode == GuidanceModes.CLOSThreePoint || launcher.GuidanceMode == GuidanceModes.CLOSLead; @@ -8263,7 +8277,7 @@ void SearchForLaserPoint() } else { - foundCam = BDATargetManager.GetLaserTarget((BDModularGuidance)ml, false, Team); + foundCam = BDATargetManager.GetLaserTarget((BDModularGuidance)currMissile, false, Team); } if (foundCam) @@ -8276,7 +8290,7 @@ void SearchForLaserPoint() } } - void SearchForHeatTarget(MissileBase currMissile, TargetInfo targetMissile = null) + void SearchForHeatTarget(MissileBase currMissile, TargetInfo missileTgt = null) { if (currMissile == null) { @@ -8306,7 +8320,7 @@ void SearchForHeatTarget(MissileBase currMissile, TargetInfo targetMissile = nul // If not missile with uncaged lock or torpedo if (!currMissile.uncagedLock && currMissile.GuidanceMode != MissileBase.GuidanceModes.SLW) { - heatTarget = BDATargetManager.GetHeatTarget(vessel, vessel, new Ray(adjustedPos, forward), TargetSignatureData.noTarget, scanRadius, currMissile.heatThreshold, currMissile.frontAspectHeatModifier, currMissile.uncagedLock, currMissile.targetCoM, currMissile.lockedSensorFOVBias, currMissile.lockedSensorVelocityBias, currMissile.lockedSensorVelocityMagnitudeBias, currMissile.lockedSensorMinAngularVelocity, this, targetMissile != null ? targetMissile : guardMode ? currentTarget : null, IFF: currMissile.hasIFF); + heatTarget = BDATargetManager.GetHeatTarget(vessel, vessel, new Ray(adjustedPos, forward), TargetSignatureData.noTarget, scanRadius, currMissile.heatThreshold, currMissile.frontAspectHeatModifier, currMissile.uncagedLock, currMissile.targetCoM, currMissile.lockedSensorFOVBias, currMissile.lockedSensorVelocityBias, currMissile.lockedSensorVelocityMagnitudeBias, currMissile.lockedSensorMinAngularVelocity, this, missileTgt != null ? missileTgt : guardMode ? currentTarget : null, IFF: currMissile.hasIFF); return; } @@ -8318,7 +8332,7 @@ void SearchForHeatTarget(MissileBase currMissile, TargetInfo targetMissile = nul // Prioritize radar target if (vesselRadarData.locked) { - if (targetMissile == null) //uncaged radar lock + if (missileTgt == null) //uncaged radar lock { currTarget = vesselRadarData.lockedTargetData.targetData; radarTarget = true; @@ -8328,7 +8342,7 @@ void SearchForHeatTarget(MissileBase currMissile, TargetInfo targetMissile = nul List possibleTargets = vesselRadarData.GetLockedTargets(); for (int i = 0; i < possibleTargets.Count; i++) { - if (possibleTargets[i].vessel == targetMissile.Vessel) + if (possibleTargets[i].vessel == missileTgt.Vessel) { currTarget = possibleTargets[i]; radarTarget = true; @@ -8339,10 +8353,10 @@ void SearchForHeatTarget(MissileBase currMissile, TargetInfo targetMissile = nul } else if (_irstsEnabled && !(HMDcond && _isHMDEnabled)) // Try IRST if we don't have an HMD { - if (targetMissile == null) + if (missileTgt == null) currTarget = vesselRadarData.activeIRTarget(guardTarget, this); //point seeker at active target's IR return else - currTarget = vesselRadarData.activeIRTarget(targetMissile.Vessel, this); + currTarget = vesselRadarData.activeIRTarget(missileTgt.Vessel, this); } } @@ -8353,7 +8367,7 @@ void SearchForHeatTarget(MissileBase currMissile, TargetInfo targetMissile = nul if (currTarget.exists) { // Override target if angle is > 2 * lockedSensorFOV - if (VectorUtils.Angle(currTarget.position - vessel.CoM, targetMissile ? (targetMissile.position - vessel.CoM) : _HMDray.direction) > 2f * currMissile.lockedSensorFOV) + if (VectorUtils.Angle(currTarget.position - vessel.CoM, missileTgt ? (missileTgt.position - vessel.CoM) : _HMDray.direction) > 2f * currMissile.lockedSensorFOV) { currTarget = TargetSignatureData.noTarget; } @@ -8361,9 +8375,9 @@ void SearchForHeatTarget(MissileBase currMissile, TargetInfo targetMissile = nul if (!currTarget.exists) { - if (targetMissile) + if (missileTgt) { - HMDTarget = CanSeeTarget(targetMissile.MissileBaseModule); + HMDTarget = CanSeeTarget(missileTgt.MissileBaseModule); } else { @@ -8390,9 +8404,9 @@ void SearchForHeatTarget(MissileBase currMissile, TargetInfo targetMissile = nul { if (HMDTarget) { - if (targetMissile) + if (missileTgt) { - direction = VectorUtils.Angle(targetMissile.position - currMissile.MissileReferenceTransform.position, forward) < maxOffBoresight ? (targetMissile.position - adjustedPos) + direction = VectorUtils.Angle(missileTgt.position - currMissile.MissileReferenceTransform.position, forward) < maxOffBoresight ? (missileTgt.position - adjustedPos) : forward; } else @@ -8407,8 +8421,8 @@ void SearchForHeatTarget(MissileBase currMissile, TargetInfo targetMissile = nul // TECHNICALLY uncagedLock = false missiles should NOT be allowed to point in any direction other than forward prior to launch, but that may be too restrictive... // remove AI target check/move to a missile .cfg option to allow older gen heaters? if (currMissile.GuidanceMode != MissileBase.GuidanceModes.SLW || (currMissile.GuidanceMode == MissileBase.GuidanceModes.SLW && currMissile.activeRadarRange > 0)) - heatTarget = BDATargetManager.GetHeatTarget(vessel, vessel, new Ray(adjustedPos, direction), TargetSignatureData.noTarget, scanRadius, currMissile.heatThreshold, currMissile.frontAspectHeatModifier, currMissile.uncagedLock, currMissile.targetCoM, currMissile.lockedSensorFOVBias, currMissile.lockedSensorVelocityBias, currMissile.lockedSensorVelocityMagnitudeBias, currMissile.lockedSensorMinAngularVelocity, this, targetMissile != null ? targetMissile : guardMode ? currentTarget : null, IFF: currMissile.hasIFF); - else heatTarget = BDATargetManager.GetAcousticTarget(vessel, vessel, new Ray(adjustedPos, direction), TargetSignatureData.noTarget, scanRadius, currMissile.heatThreshold, currMissile.targetCoM, currMissile.lockedSensorFOVBias, currMissile.lockedSensorVelocityBias, currMissile.lockedSensorVelocityMagnitudeBias, currMissile.lockedSensorMinAngularVelocity, this, targetMissile != null ? targetMissile : guardMode ? currentTarget : null, IFF: currMissile.hasIFF); + heatTarget = BDATargetManager.GetHeatTarget(vessel, vessel, new Ray(adjustedPos, direction), TargetSignatureData.noTarget, scanRadius, currMissile.heatThreshold, currMissile.frontAspectHeatModifier, currMissile.uncagedLock, currMissile.targetCoM, currMissile.lockedSensorFOVBias, currMissile.lockedSensorVelocityBias, currMissile.lockedSensorVelocityMagnitudeBias, currMissile.lockedSensorMinAngularVelocity, this, missileTgt != null ? missileTgt : guardMode ? currentTarget : null, IFF: currMissile.hasIFF); + else heatTarget = BDATargetManager.GetAcousticTarget(vessel, vessel, new Ray(adjustedPos, direction), TargetSignatureData.noTarget, scanRadius, currMissile.heatThreshold, currMissile.targetCoM, currMissile.lockedSensorFOVBias, currMissile.lockedSensorVelocityBias, currMissile.lockedSensorVelocityMagnitudeBias, currMissile.lockedSensorMinAngularVelocity, this, missileTgt != null ? missileTgt : guardMode ? currentTarget : null, IFF: currMissile.hasIFF); } bool CrossCheckWithRWR(TargetInfo v) @@ -8903,7 +8917,7 @@ void GuardMode() //&& (CurrentMissile.TargetingMode != MissileBase.TargetingModes.Radar || (vesselRadarData != null && (!vesselRadarData.locked || vesselRadarData.lockedTargetData.vessel == guardTarget)))) // Allow firing multiple missiles at the same target. FIXME This is a stop-gap until proper multi-locking support is available. { if (BDArmorySettings.DEBUG_MISSILES) Debug.Log($"[BDArmory.MissileFire]: {vessel.vesselName} firing {(unguidedWeapon ? "unguided" : "")} missile"); - StartCoroutine(GuardMissileRoutine(guardTarget, CurrentMissile)); + StartCoroutine(GuardMissileRoutine(currentTarget, CurrentMissile)); } } else if (BDArmorySettings.DEBUG_MISSILES) @@ -9914,7 +9928,7 @@ public void PointDefenseTurretFiring() //missileTarget = targetVessel; //if (logging) // Debug.Log($"[BDArmory.MissileFire] firing interceptor missile: {currMissile.shortName} at {targetVessel.name}"); - StartCoroutine(GuardMissileRoutine(targetVessel, currMissile)); + StartCoroutine(GuardMissileRoutine(PDMslTgts[MissileID], currMissile)); break; } else @@ -10049,7 +10063,7 @@ public bool GetLaunchAuthorization(Vessel targetV, MissileFire mf, MissileBase m target = MissileGuidance.GetAirToAirFireSolution(missile, targetV); } - Vector3 missilePos = missile.transform.position; + Vector3 missilePos = missile.MissileReferenceTransform.position; float boresightAngle = missile.maxOffBoresight * ((mf.vessel.LandedOrSplashed || targetV.LandedOrSplashed || missile.uncagedLock) ? 0.75f : 0.35f); // Allow launch at close to maxOffBoresight for ground targets or missiles with allAspect = true if (unguidedWeapon || missile.TargetingMode == MissileBase.TargetingModes.None) // Override boresightAngle based on blast radius for unguidedWeapons or weapons with no targeting mode From ef32775ef66b528797d4fcc8ab5fceafbab9910b Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Sat, 21 Feb 2026 23:25:54 -0800 Subject: [PATCH 15/17] Use MissileReferenceTransform Where Appropriate --- BDArmory/Control/MissileFire.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/BDArmory/Control/MissileFire.cs b/BDArmory/Control/MissileFire.cs index d89938cff..787f7417f 100644 --- a/BDArmory/Control/MissileFire.cs +++ b/BDArmory/Control/MissileFire.cs @@ -1917,7 +1917,7 @@ public override void OnUpdate() { MissileLauncher msl = CurrentMissile as MissileLauncher; Vector3 missileForward = ml.GetForwardTransform(); - Vector3 missilePos = ml.transform.position; + Vector3 missilePos = ml.MissileReferenceTransform.position; for (int i = 0; i < rwr.pingsData.Length; i++) { Vector3 position; @@ -2425,7 +2425,7 @@ void OnGUI() if (incomingMissileVessel) { GUIUtils.DrawLineBetweenWorldPositions(part.transform.position, - incomingMissileVessel.transform.position, 5, Color.cyan); + incomingMissileVessel.CoM, 5, Color.cyan); } if (guardTarget != null) GUIUtils.DrawLineBetweenWorldPositions(guardTarget.LandedOrSplashed ? guardTarget.CoM + ((guardTarget.vesselSize.y / 2) * vessel.up) : guardTarget.CoM, @@ -6259,7 +6259,7 @@ bool SmartPickWeapon_EngagementEnvelope(TargetInfo target) candidateAccel = 1; candidatePriority = Mathf.RoundToInt(mm.priority); - if (vessel.Splashed && FlightGlobals.getAltitudeAtPos(mlauncher.transform.position) < -5) continue; + if (vessel.Splashed && FlightGlobals.getAltitudeAtPos(mlauncher.MissileReferenceTransform.position) < -5) continue; if (targetWeapon != null && targetWeaponPriority > candidatePriority) continue; //keep higher priority weapon if (candidateDetDist + candidateAccel > targetWeaponTDPS) @@ -6651,7 +6651,7 @@ bool SmartPickWeapon_EngagementEnvelope(TargetInfo target) bool antiRad = mlauncher.TargetingMode == MissileBase.TargetingModes.AntiRad; float heatThresh = mlauncher.heatThreshold; if (EMP && target.isDebilitated) continue; - if (vessel.Splashed && (!surfaceAI || surfaceAI.SurfaceType != AIUtils.VehicleMovementType.Submarine) && (BDArmorySettings.BULLET_WATER_DRAG && FlightGlobals.getAltitudeAtPos(mlauncher.transform.position) < -10)) continue; //allow submarine-mounted missiles; new launch depth check in launchAuth + if (vessel.Splashed && (!surfaceAI || surfaceAI.SurfaceType != AIUtils.VehicleMovementType.Submarine) && (BDArmorySettings.BULLET_WATER_DRAG && FlightGlobals.getAltitudeAtPos(mlauncher.MissileReferenceTransform.position) < -10)) continue; //allow submarine-mounted missiles; new launch depth check in launchAuth if (targetWeapon != null && targetWeaponPriority > candidatePriority) continue; //keep higher priority weapon @@ -6732,7 +6732,7 @@ bool SmartPickWeapon_EngagementEnvelope(TargetInfo target) { candidateTDPS *= 0.001f; //no laserdot, skip to something else unless nothing else available } - float fovAngle = VectorUtils.Angle(mlauncher.GetForwardTransform(), targetVessel.CoM - mlauncher.transform.position); + float fovAngle = VectorUtils.Angle(mlauncher.GetForwardTransform(), targetVessel.CoM - mlauncher.MissileReferenceTransform.position); if (fovAngle > mlauncher.missileFireAngle && mlauncher.missileFireAngle < mlauncher.maxOffBoresight * 0.75f) { candidateTDPS *= mlauncher.missileFireAngle / fovAngle; //missile is clamped to a narrow boresight - do we have anything with a wider FoV we should start with? @@ -6746,7 +6746,7 @@ bool SmartPickWeapon_EngagementEnvelope(TargetInfo target) //candidateTurning = ((MissileLauncher)item.Current).maxTurnRateDPS; //for anti-aircraft, prioritize detonation dist and turn capability candidatePriority = Mathf.RoundToInt(mm.priority); - if ((!surfaceAI || surfaceAI.SurfaceType != AIUtils.VehicleMovementType.Submarine) && vessel.Splashed && (BDArmorySettings.BULLET_WATER_DRAG && FlightGlobals.getAltitudeAtPos(mlauncher.transform.position) < 0)) continue; + if ((!surfaceAI || surfaceAI.SurfaceType != AIUtils.VehicleMovementType.Submarine) && vessel.Splashed && (BDArmorySettings.BULLET_WATER_DRAG && FlightGlobals.getAltitudeAtPos(mlauncher.MissileReferenceTransform.position) < 0)) continue; if (targetWeapon != null && targetWeaponPriority > candidatePriority) continue; //keep higher priority weapon @@ -9581,7 +9581,7 @@ public void PointDefenseTurretFiring() if (TurretID >= PDMslTgts.Count) TurretID = 0; if (PDMslTgts.Count > 0) { - if (PDMslTgts[TurretID].Vessel != null && PDMslTgts[TurretID].transform.position.FurtherFromThan(weapon.fireTransforms[0].position, weapon.engageRangeMax * 1.25f)) TurretID = 0; //reset cycle so out of range guns engage closer targets + if (PDMslTgts[TurretID].Vessel != null && PDMslTgts[TurretID].position.FurtherFromThan(weapon.fireTransforms[0].position, weapon.engageRangeMax * 1.25f)) TurretID = 0; //reset cycle so out of range guns engage closer targets if (PDMslTgts[TurretID].Vessel != null) { bool viableTarget = true; From d1cbef8c001900dbe4a1b3fd5b0462483105f540 Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Sun, 22 Feb 2026 17:41:53 -0800 Subject: [PATCH 16/17] Re-Organized UpdateRadarTarget - Re-organize envelope checks - Add more telemetry to RadarLOAL --- BDArmory/Weapons/Missiles/MissileBase.cs | 127 +++++++++++++---------- 1 file changed, 72 insertions(+), 55 deletions(-) diff --git a/BDArmory/Weapons/Missiles/MissileBase.cs b/BDArmory/Weapons/Missiles/MissileBase.cs index 823e35a32..103a6a008 100644 --- a/BDArmory/Weapons/Missiles/MissileBase.cs +++ b/BDArmory/Weapons/Missiles/MissileBase.cs @@ -1140,44 +1140,49 @@ protected void UpdateRadarTarget() if (scannedTargets[i].exists && (scannedTargets[i].predictedPosition - radarTarget.predictedPosition).sqrMagnitude < sqrThresh) { //re-check engagement envelope, only lock appropriate targets - if (CheckTargetEngagementEnvelope(scannedTargets[i].targetInfo) && (!hasIFF || !Team.IsFriendly(scannedTargets[i].Team))) + if (!CheckTargetEngagementEnvelope(scannedTargets[i].targetInfo)) continue; + + if (hasIFF && Team.IsFriendly(scannedTargets[i].Team)) continue; + + radarTarget = scannedTargets[i]; + TargetAcquired = true; + radarLOALSearching = false; + //if (weaponClass == WeaponClasses.SLW) + // TargetPosition = radarTarget.predictedPosition + (radarTarget.velocity * Time.fixedDeltaTime); + //else + TargetPosition = radarTarget.predictedPositionWithChaffFactor(chaffEffectivity, chaffNotchVFac, chaffNotchRFac); + + TargetVelocity = radarTarget.velocity; + TargetAcceleration = radarTarget.acceleration; + _lockFailTimer = 0; + if (!ActiveRadar && Time.time - TimeFired > 1) { - radarTarget = scannedTargets[i]; - TargetAcquired = true; - radarLOALSearching = false; - //if (weaponClass == WeaponClasses.SLW) - // TargetPosition = radarTarget.predictedPosition + (radarTarget.velocity * Time.fixedDeltaTime); - //else - TargetPosition = radarTarget.predictedPositionWithChaffFactor(chaffEffectivity, chaffNotchVFac, chaffNotchRFac); - - TargetVelocity = radarTarget.velocity; - TargetAcceleration = radarTarget.acceleration; - _lockFailTimer = 0; - if (!ActiveRadar && Time.time - TimeFired > 1) + if (locksCount == 0) { - if (locksCount == 0) + if (weaponClass == WeaponClasses.SLW) { - if (weaponClass == WeaponClasses.SLW) - RadarWarningReceiver.PingRWR(ray, lockedSensorFOV, RadarWarningReceiver.RWRThreatTypes.Torpedo, 2f, vessel); - else - RadarWarningReceiver.PingRWR(ray, lockedSensorFOV, RadarWarningReceiver.RWRThreatTypes.MissileLaunch, 2f, vessel); - if (BDArmorySettings.DEBUG_MISSILES) Debug.Log($"[BDArmory.MissileBase]: Pitbull! Radar missilebase has gone active. Radar sig strength: {radarTarget.signalStrength:0.0}"); + RadarWarningReceiver.PingRWR(ray, lockedSensorFOV, RadarWarningReceiver.RWRThreatTypes.Torpedo, 2f, vessel); } - else if (locksCount > 2) + else { - guidanceActive = false; - checkMiss = true; - if (BDArmorySettings.DEBUG_MISSILES) - { - Debug.Log("[BDArmory.MissileBase]: Active Radar guidance failed. Radar missileBase reached max re-lock attempts."); - } + RadarWarningReceiver.PingRWR(ray, lockedSensorFOV, RadarWarningReceiver.RWRThreatTypes.MissileLaunch, 2f, vessel); } - locksCount++; + if (BDArmorySettings.DEBUG_MISSILES) Debug.Log($"[BDArmory.MissileBase]: Pitbull! Radar missilebase has gone active. Radar sig strength: {radarTarget.signalStrength:0.0}"); } - ActiveRadar = true; - updateRadarCS = true; - return; + else if (locksCount > 2) + { + guidanceActive = false; + checkMiss = true; + if (BDArmorySettings.DEBUG_MISSILES) + { + Debug.Log("[BDArmory.MissileBase]: Active Radar guidance failed. Radar missileBase reached max re-lock attempts."); + } + } + locksCount++; } + ActiveRadar = true; + updateRadarCS = true; + return; } //if (!scannedTargets[i].exists) // if (BDArmorySettings.DEBUG_MISSILES) Debug.Log($"[BDArmory.MissileBase][Radar Active]: Target: {i} doesn't exist!."); @@ -1267,40 +1272,52 @@ protected void UpdateRadarTarget() if (scannedTargets[i].exists && !useSoughtTarget || (tempDist = (scannedTargets[i].predictedPosition - soughtTarget).sqrMagnitude) < 1000000f) { //re-check engagement envelope, only lock appropriate targets - if (CheckTargetEngagementEnvelope(scannedTargets[i].targetInfo)) + if (!CheckTargetEngagementEnvelope(scannedTargets[i].targetInfo)) { - if (hasIFF && Team.IsFriendly(scannedTargets[i].targetInfo.Team)) continue;//Don't lock friendlies + if (BDArmorySettings.DEBUG_MISSILES) Debug.Log($"[BDArmory.MissileBase][Radar LOAL]: Target: {scannedTargets[i].vessel.name} rejected due to target envelope!"); + continue; + } - if (!useSoughtTarget) - { - (tempDist, Vector3 currDir) = (scannedTargets[i].predictedPosition - soughtTarget).MagNorm(); - currAngle = Mathf.Rad2Deg * Mathf.Acos(Vector3.Dot(currDir, forward)); - if (currAngle > (smallestAngle + 5f)) continue; // Look for the smallest angle, give 5 degrees of wiggle room. - // Look for closest target to the missile - currDist = tempDist; - } - else + //Don't lock friendlies + if (hasIFF && Team.IsFriendly(scannedTargets[i].targetInfo.Team)) + { + if (BDArmorySettings.DEBUG_MISSILES) Debug.Log($"[BDArmory.MissileBase][Radar LOAL]: Target: {scannedTargets[i].vessel.name} rejected due to IFF!"); + continue; + } + + if (!useSoughtTarget) + { + (tempDist, Vector3 currDir) = (scannedTargets[i].predictedPosition - soughtTarget).MagNorm(); + currAngle = Mathf.Rad2Deg * Mathf.Acos(Vector3.Dot(currDir, forward)); + if (currAngle > (smallestAngle + 5f)) { - // Look for closest target to the previous target location - currDist = tempDist; + if (BDArmorySettings.DEBUG_MISSILES) Debug.Log($"[BDArmory.MissileBase][Radar LOAL]: Target: {scannedTargets[i].vessel.name} rejected due to angle!"); + continue; // Look for the smallest angle, give 5 degrees of wiggle room. } + // Look for closest target to the missile + currDist = tempDist; + } + else + { + // Look for closest target to the previous target location + currDist = tempDist; + } - if (BDArmorySettings.DEBUG_MISSILES) Debug.Log($"[BDArmory.MissileBase][Radar LOAL]: Target: {scannedTargets[i].vessel.name} has {(targetVessel == null ? "currDist" : "currSqrDist")}: {currDist}."); + if (BDArmorySettings.DEBUG_MISSILES) Debug.Log($"[BDArmory.MissileBase][Radar LOAL]: Target: {scannedTargets[i].vessel.name} has {(targetVessel == null ? "currDist" : "currSqrDist")}: {currDist}."); - if (currDist < smallestDist) + if (currDist < smallestDist) + { + if (!useSoughtTarget && currAngle < smallestAngle) { - if (!useSoughtTarget && currAngle < smallestAngle) - { - smallestAngle = currAngle; - } - smallestDist = currDist; - lockedTarget = scannedTargets[i]; - ActiveRadar = true; - updateRadarCS = true; - //if (BDArmorySettings.DEBUG_MISSILES) Debug.Log($"[BDArmory.MissileBase][Radar LOAL]: Target: {scannedTargets[i].vessel.name} selected."); + smallestAngle = currAngle; } - //return; + smallestDist = currDist; + lockedTarget = scannedTargets[i]; + ActiveRadar = true; + updateRadarCS = true; + //if (BDArmorySettings.DEBUG_MISSILES) Debug.Log($"[BDArmory.MissileBase][Radar LOAL]: Target: {scannedTargets[i].vessel.name} selected."); } + //return; } //if (!scannedTargets[i].exists) //if (BDArmorySettings.DEBUG_MISSILES) Debug.Log($"[BDArmory.MissileBase][Radar LOAL]: Target: {i} doesn't exist!."); From 8b13abcd4fd1b3f6c23fa06189adb00bcc3798a1 Mon Sep 17 00:00:00 2001 From: BillNyeTheIE Date: Tue, 24 Feb 2026 18:44:35 -0800 Subject: [PATCH 17/17] Fix Some Bullet Detonation Routines - Fix missing coroutine calls --- BDArmory/Ammo/PooledBullet.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/BDArmory/Ammo/PooledBullet.cs b/BDArmory/Ammo/PooledBullet.cs index 72eb74ae9..f222804f1 100644 --- a/BDArmory/Ammo/PooledBullet.cs +++ b/BDArmory/Ammo/PooledBullet.cs @@ -539,15 +539,22 @@ public void PostCollisions() { if (fuzeType == BulletFuzeTypes.Delay || fuzeType == BulletFuzeTypes.Penetrating) { - fuzeTriggered = true; - delayedDetonationRoutine = StartCoroutine(DelayedDetonationRoutine()); + if (!fuzeTriggered) + { + fuzeTriggered = true; + delayedDetonationRoutine = StartCoroutine(DelayedDetonationRoutine()); + } } else //if (fuzeType != BulletFuzeTypes.None) { if (HEType != PooledBulletTypes.Slug) + { ExplosionFx.CreateExplosion(currentPosition, tntMass, explModelPath, explSoundPath, ExplosionSourceType.Bullet, caliber, null, sourceVesselName, null, null, default, -1, false, bulletMass, -1, dmgMult); + } if (nuclear) + { NukeFX.CreateExplosion(currentPosition, ExplosionSourceType.Bullet, sourceVesselName, bullet.DisplayName, 0, tntMass * 200, tntMass, tntMass, EMP, blastSoundPath, flashModelPath, shockModelPath, blastModelPath, plumeModelPath, debrisModelPath, "", ""); + } hasDetonated = true; if (BDArmorySettings.waterHitEffect && FlightGlobals.currentMainBody.ocean) FXMonger.Splash(currentPosition, caliber / 2); KillBullet(); @@ -1510,7 +1517,11 @@ bool BulletHitAnalysis(BulletHit bulletHit, float period) // Should penetrating fuzes also get triggered here? I guess they should... if (fuzeType == BulletFuzeTypes.Delay || fuzeType == BulletFuzeTypes.Penetrating) { - fuzeTriggered = true; + if (!fuzeTriggered) + { + fuzeTriggered = true; + delayedDetonationRoutine = StartCoroutine(DelayedDetonationRoutine()); + } } } }