< Summary

Class:Interceptor
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Interceptors/Interceptor.cs
Covered lines:0
Uncovered lines:166
Coverable lines:166
Total lines:264
Line coverage:0% (0 of 166)
Covered branches:0
Total branches:0
Covered methods:0
Total methods:17
Method coverage:0% (0 of 17)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
Interceptor()0%2100%
Awake()0%2100%
IsAssignable()0%2100%
UpdateReady(...)0%2100%
FixedUpdate()0%6200%
UpdateBoost(...)0%20400%
UpdateMidCourse(...)0%2100%
CalculateAccelerationInput(...)0%20400%
UpdateTargetModel(...)0%1101000%
OnTriggerEnter(...)0%90900%
TerminateAgent()0%2100%
OnDestroy()0%20400%
AttachMissileTrailEffect()0%72800%
ReturnParticleToManager()0%20400%
UpdateMissileTrailEffect()0%20400%
DetachMissileTrail()0%20400%
DrawDebugVectors()0%12300%

File(s)

/github/workspace/Assets/Scripts/Interceptors/Interceptor.cs

#LineLine coverage
 1using System.Collections;
 2using System.Collections.Generic;
 3using UnityEngine;
 4
 5public class Interceptor : Agent {
 6  [SerializeField]
 07  private float _navigationGain = 3f;  // Typically 3-5.
 8
 9  [SerializeField]
 010  protected bool _showDebugVectors = true;
 11
 12  private GameObject _missileTrailEffect;
 013  private bool _missileTrailEffectAttached = false;
 14
 15  private Coroutine _returnParticleToManagerCoroutine;
 16
 17  private Vector3 _accelerationInput;
 18
 019  private double _elapsedTime = 0;
 20
 021  protected override void Awake() {
 022    base.Awake();
 023    SetFlightPhase(FlightPhase.INITIALIZED);
 024  }
 25
 026  public override bool IsAssignable() {
 027    bool assignable = !HasAssignedTarget();
 028    return assignable;
 029  }
 30
 031  protected override void UpdateReady(double deltaTime) {
 032    Vector3 accelerationInput = Vector3.zero;
 033    Vector3 acceleration = CalculateAcceleration(accelerationInput);
 034    GetComponent<Rigidbody>().AddForce(acceleration, ForceMode.Acceleration);
 035  }
 36
 037  protected override void FixedUpdate() {
 038    base.FixedUpdate();
 039    if (_showDebugVectors) {
 040      DrawDebugVectors();
 041    }
 042  }
 43
 044  protected override void UpdateBoost(double deltaTime) {
 045    if (_missileTrailEffect == null) {
 046      AttachMissileTrailEffect();
 047    }
 048    UpdateMissileTrailEffect();
 49
 50    // Calculate the boost acceleration.
 051    float boostAcceleration =
 52        (float)((staticConfig.BoostConfig?.BoostAcceleration ?? 0) * Constants.kGravity);
 053    Vector3 boostAccelerationVector = boostAcceleration * transform.forward;
 54
 55    // Add the PN acceleration to the boost acceleration.
 056    Vector3 controllerAcceleration = CalculateAccelerationInput(deltaTime);
 057    Vector3 accelerationInput = boostAccelerationVector + controllerAcceleration;
 58
 59    // Calculate the total acceleration.
 060    Vector3 acceleration = CalculateAcceleration(accelerationInput);
 61
 62    // Apply the acceleration.
 063    GetComponent<Rigidbody>().AddForce(acceleration, ForceMode.Acceleration);
 064  }
 65
 066  protected override void UpdateMidCourse(double deltaTime) {
 067    UpdateMissileTrailEffect();
 68
 069    _elapsedTime += deltaTime;
 070    Vector3 accelerationInput = CalculateAccelerationInput(deltaTime);
 71
 72    // Calculate and set the total acceleration.
 073    Vector3 acceleration = CalculateAcceleration(accelerationInput);
 074    GetComponent<Rigidbody>().AddForce(acceleration, ForceMode.Acceleration);
 075  }
 76
 077  private Vector3 CalculateAccelerationInput(double deltaTime) {
 078    Vector3 accelerationInput = Vector3.zero;
 079    float maxNormalAcceleration = CalculateMaxNormalAcceleration();
 80
 081    if (!HasAssignedTarget()) {
 82      // No assigned target: do not generate control input.
 83      // Gravity and drag are applied centrally in AerialAgent.CalculateAcceleration,
 84      // so returning zero here ensures idle missiles/carriers still experience gravity
 85      // and do not artificially hover.
 086      _accelerationInput = Vector3.zero;
 087      return _accelerationInput;
 88    }
 89
 090    UpdateTargetModel(deltaTime);
 91
 92    // Check whether the threat should be considered a miss.
 093    SensorOutput sensorOutput = GetComponent<Sensor>().Sense(_target);
 94    // TODO(dlovell): This causes trouble with the Fateh 110B (high-speed threats).
 95    // if (sensorOutput.velocity.range > 1000f) {
 96    //   HandleInterceptMiss();
 97    //   return Vector3.zero;
 98    // }
 99
 100    IController controller;
 0101    switch (agentConfig.DynamicConfig.FlightConfig.ControllerType) {
 0102      case Configs.ControllerType.ProportionalNavigation: {
 0103        controller = new PnController(this, _navigationGain);
 0104        break;
 105      }
 0106      case Configs.ControllerType.AugmentedProportionalNavigation: {
 0107        controller = new ApnController(this, _navigationGain);
 0108        break;
 109      }
 0110      default: {
 0111        Debug.LogError(
 112            $"Controller type {agentConfig.DynamicConfig.FlightConfig.ControllerType} not found.");
 0113        controller = new PnController(this, _navigationGain);
 0114        break;
 115      }
 116    }
 0117    accelerationInput = controller.Plan();
 118
 119    // Clamp the normal acceleration input to the maximum normal acceleration.
 0120    accelerationInput = Vector3.ClampMagnitude(accelerationInput, maxNormalAcceleration);
 0121    _accelerationInput = accelerationInput;
 0122    return accelerationInput;
 0123  }
 124
 0125  private void UpdateTargetModel(double deltaTime) {
 126    // Skip if target/model/sensor is unavailable. This prevents null dereferences
 127    // during phases before assignment or when configs are incomplete.
 0128    if (_targetModel == null || _target == null) {
 0129      return;
 130    }
 131
 0132    var sensorConfig = agentConfig?.DynamicConfig?.SensorConfig;
 0133    if (sensorConfig == null || sensorConfig.Frequency <= 0f) {
 0134      return;
 135    }
 136
 0137    _elapsedTime += deltaTime;
 0138    float sensorUpdatePeriod = 1f / sensorConfig.Frequency;
 0139    if (_elapsedTime >= sensorUpdatePeriod) {
 140      // TODO: Implement guidance filter to estimate state from the sensor output.
 141      // For now, we'll use the threat's actual state.
 0142      _targetModel.SetPosition(_target.GetPosition());
 0143      _targetModel.SetVelocity(_target.GetVelocity());
 0144      _targetModel.SetAcceleration(_target.GetAcceleration());
 0145      _elapsedTime = 0;
 0146    }
 0147  }
 148
 0149  private void OnTriggerEnter(Collider other) {
 150    // Check if the interceptor hit the floor with a negative vertical speed.
 0151    if (other.gameObject.name == "Floor" && Vector3.Dot(GetVelocity(), Vector3.up) < 0) {
 0152      HandleHitGround();
 0153    }
 154
 155    // Check if the collision is with another agent.
 0156    Agent otherAgent = other.gameObject.GetComponentInParent<Agent>();
 0157    if (otherAgent != null && otherAgent.GetComponent<Threat>() != null &&
 0158        _target == otherAgent as Threat) {
 159      // Check kill probability before marking as hit.
 0160      float killProbability = otherAgent.staticConfig.HitConfig?.KillProbability ?? 0;
 161
 0162      if (Random.value <= killProbability) {
 163        // Mark both this agent and the other agent as hit.
 0164        HandleInterceptHit(otherAgent);
 0165        otherAgent.HandleTargetIntercepted();
 0166      } else {
 0167        HandleInterceptMiss();
 0168      }
 0169    }
 0170  }
 171
 0172  public override void TerminateAgent() {
 0173    DetachMissileTrail();
 0174    base.TerminateAgent();
 0175  }
 176
 0177  public void OnDestroy() {
 0178    if (_returnParticleToManagerCoroutine != null) {
 0179      StopCoroutine(_returnParticleToManagerCoroutine);
 0180    }
 0181    if (_missileTrailEffect != null && ParticleManager.Instance != null) {
 0182      ParticleManager.Instance.ReturnMissileTrailParticle(_missileTrailEffect);
 0183      _missileTrailEffect = null;
 0184    }
 0185  }
 186
 0187  private void AttachMissileTrailEffect() {
 0188    if (_missileTrailEffect == null) {
 0189      _missileTrailEffect = ParticleManager.Instance.RequestMissileTrailParticle();
 0190      if (_missileTrailEffect != null) {
 0191        _missileTrailEffect.transform.parent = transform;
 0192        _missileTrailEffect.transform.localPosition = Vector3.zero;
 0193        _missileTrailEffectAttached = true;
 0194        ParticleSystem particleSystem = _missileTrailEffect.GetComponent<ParticleSystem>();
 0195        float duration = particleSystem.main.duration;
 196
 197        // Extend the duration of the missile trail effect to be the same as the boost time.
 0198        if (duration < (staticConfig.BoostConfig?.BoostTime ?? 0)) {
 0199          ParticleSystem.MainModule mainModule = particleSystem.main;
 0200          mainModule.duration = staticConfig.BoostConfig?.BoostTime ?? 0;
 0201        }
 202
 0203        _returnParticleToManagerCoroutine = StartCoroutine(ReturnParticleToManager(duration * 2f));
 0204        particleSystem.Play();
 0205      }
 0206    }
 0207  }
 208
 0209  private IEnumerator ReturnParticleToManager(float delay) {
 0210    yield return new WaitForSeconds(delay);
 0211    if (_missileTrailEffect != null) {
 0212      ParticleManager.Instance.ReturnMissileTrailParticle(_missileTrailEffect);
 0213      _missileTrailEffect = null;
 0214      _missileTrailEffectAttached = false;
 0215    }
 0216  }
 217
 0218  private void UpdateMissileTrailEffect() {
 0219    if (_missileTrailEffect == null || !_missileTrailEffectAttached) {
 0220      return;
 221    }
 222
 223    // Get the particle effect duration time.
 0224    float duration = _missileTrailEffect.GetComponent<ParticleSystem>().main.duration;
 0225    if (_timeSinceBoost > duration) {
 0226      DetachMissileTrail();
 0227    }
 0228  }
 229
 0230  private void DetachMissileTrail() {
 0231    if (_missileTrailEffect != null && _missileTrailEffectAttached) {
 0232      Vector3 currentPosition = _missileTrailEffect.transform.position;
 0233      _missileTrailEffect.transform.SetParent(null);
 0234      _missileTrailEffect.transform.position = currentPosition;
 0235      _missileTrailEffectAttached = false;
 236      // Stop emitting particles.
 0237      ParticleSystem particleSystem = _missileTrailEffect.GetComponent<ParticleSystem>();
 0238      particleSystem.Stop();
 0239    }
 0240  }
 241
 0242  protected virtual void DrawDebugVectors() {
 0243    if (_target != null) {
 244      // Line of sight.
 0245      Debug.DrawLine(transform.position, _target.transform.position, new Color(1, 1, 1, 0.15f));
 246
 247      // Velocity vector.
 0248      Debug.DrawRay(transform.position, GetVelocity() * 0.01f, new Color(0, 0, 1, 0.15f));
 249
 250      // Current forward direction.
 0251      Debug.DrawRay(transform.position, transform.forward * 5f, Color.yellow);
 252
 253      // Pitch axis (right).
 0254      Debug.DrawRay(transform.position, transform.right * 5f, Color.red);
 255
 256      // Yaw axis (up).
 0257      Debug.DrawRay(transform.position, transform.up * 5f, Color.magenta);
 258
 0259      if (_accelerationInput != null) {
 0260        Debug.DrawRay(transform.position, _accelerationInput * 1f, Color.green);
 0261      }
 0262    }
 0263  }
 264}