< Summary

Class:ThreatBase
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Threats/ThreatBase.cs
Covered lines:65
Uncovered lines:42
Coverable lines:107
Total lines:165
Line coverage:60.7% (65 of 107)
Covered branches:0
Total branches:0
Covered methods:10
Total methods:12
Method coverage:83.3% (10 of 12)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
LookupPowerTable(...)0%110100%
HandleIntercept()0%6200%
Start()0%5.045088%
FixedUpdate()0%7.147085.71%
UpdateAgentConfig()0%18.7411060%
FindClosestPursuer()0%57.9310021.74%
OnTriggerEnter(...)0%90900%

File(s)

/github/workspace/Assets/Scripts/Threats/ThreatBase.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using UnityEngine;
 5
 6// Base implementation of a threat.
 7public abstract class ThreatBase : AgentBase, IThreat {
 8  public event Action<IThreat> OnHit;
 9  public event Action<IThreat> OnDestroyed;
 10
 11  // Speed difference threshold for applying forward acceleration.
 12  private const float _speedErrorThreshold = 1f;
 13
 14  // Power table map from the power to the speed.
 15  private Dictionary<Configs.Power, float> _powerTable;
 16
 17  // The attack behavior determines how the threat navigates towards the asset.
 747518  public IAttackBehavior AttackBehavior { get; set; }
 19
 20  // The evasion handles how the threat behaves in the vicinity of a pursuing interceptor.
 747521  public IEvasion Evasion { get; set; }
 22
 23  public IReadOnlyDictionary<Configs.Power, float> PowerTable {
 652024    get {
 747525      if (_powerTable == null) {
 95526        _powerTable =
 955027            StaticConfig.PowerTable.ToDictionary(entry => entry.Power, entry => entry.Speed);
 95528      }
 652029      return _powerTable;
 652030    }
 31  }
 32
 652033  public float LookupPowerTable(Configs.Power power) {
 652034    PowerTable.TryGetValue(power, out float speed);
 652035    return speed;
 652036  }
 37
 038  public void HandleIntercept() {
 039    OnDestroyed?.Invoke(this);
 040    Terminate();
 041  }
 42
 95543  protected override void Start() {
 95544    base.Start();
 45
 46    // The threat should target the nearest launcher.
 95547    IHierarchical target = null;
 95548    var launchers = IADS.Instance.Launchers;
 95549    if (launchers.Count == 0) {
 050      target = new FixedHierarchical(position: Vector3.zero);
 95551    } else {
 95552      float minDistanceSqr = Mathf.Infinity;
 633053      foreach (var launcher in launchers) {
 115554        float distanceSqr = (launcher.Position - Position).sqrMagnitude;
 219055        if (distanceSqr < minDistanceSqr) {
 103556          minDistanceSqr = distanceSqr;
 103557          target = launcher;
 103558        }
 115559      }
 95560    }
 95561    HierarchicalAgent.Target = target;
 95562  }
 63
 652064  protected override void FixedUpdate() {
 652065    base.FixedUpdate();
 66
 652067    float desiredSpeed = 0f;
 68    // Check whether the threat should evade any pursuer.
 652069    IAgent closestPursuer = FindClosestPursuer();
 652070    if (Evasion != null && closestPursuer != null && Evasion.ShouldEvade(closestPursuer)) {
 071      AccelerationInput = Evasion.Evade(closestPursuer);
 072      desiredSpeed = LookupPowerTable(Configs.Power.Max);
 652073    } else {
 74      // Follow the attack behavior.
 652075      (Vector3 waypoint, Configs.Power waypointPower) =
 76          AttackBehavior.GetNextWaypoint(TargetModel.Position);
 652077      AccelerationInput = Controller?.Plan(waypoint) ?? Vector3.zero;
 652078      desiredSpeed = LookupPowerTable(waypointPower);
 652079    }
 80
 81    // Limit the forward acceleration according to the desired speed.
 652082    float speedError = desiredSpeed - Speed;
 652083    Vector3 forwardAccelerationInput = Vector3.Project(AccelerationInput, Forward);
 652084    Vector3 normalAccelerationInput = Vector3.ProjectOnPlane(AccelerationInput, Forward);
 755285    if (Mathf.Abs(speedError) < _speedErrorThreshold) {
 103286      AccelerationInput = normalAccelerationInput;
 652087    } else {
 548888      float speedFactor = Mathf.Clamp01(Mathf.Abs(speedError) / _speedErrorThreshold);
 548889      AccelerationInput =
 90          normalAccelerationInput + forwardAccelerationInput * Mathf.Sign(speedError) * speedFactor;
 548891    }
 92
 652093    Acceleration = Movement?.Act(AccelerationInput) ?? Vector3.zero;
 652094    _rigidbody.AddForce(Acceleration, ForceMode.Acceleration);
 652095  }
 96
 95597  protected override void UpdateAgentConfig() {
 95598    base.UpdateAgentConfig();
 99
 100    // Set the attack behavior.
 955101    Configs.AttackBehaviorConfig attackBehaviorConfig =
 102        ConfigLoader.LoadAttackBehaviorConfig(AgentConfig.AttackBehaviorConfigFile ?? "");
 955103    switch (attackBehaviorConfig?.Type) {
 955104      case Configs.AttackType.DirectAttack: {
 955105        AttackBehavior = new DirectAttackBehavior(this, attackBehaviorConfig);
 955106        break;
 107      }
 108      case Configs.AttackType.PreplannedAttack:
 0109      case Configs.AttackType.SlalomAttack: {
 0110        Debug.LogError($"Attack behavior type {attackBehaviorConfig?.Type} is unimplemented.");
 0111        break;
 112      }
 0113      default: {
 0114        Debug.LogError($"Attack behavior type {attackBehaviorConfig?.Type} not found.");
 0115        break;
 116      }
 117    }
 118
 119    // Set the evasion.
 955120    Evasion = new OrthogonalEvasion(this);
 955121  }
 122
 6520123  private IAgent FindClosestPursuer() {
 13040124    if (HierarchicalAgent == null || !HierarchicalAgent.ActivePursuers.Any() || Sensor == null) {
 6520125      return null;
 126    }
 127
 0128    HierarchicalAgent closestAgent = null;
 0129    float minDistance = float.MaxValue;
 0130    foreach (var pursuer in HierarchicalAgent.ActivePursuers) {
 0131      if (pursuer is HierarchicalAgent agent) {
 0132        SensorOutput sensorOutput = Sensor.Sense(agent);
 0133        if (sensorOutput.Position.Range < minDistance) {
 0134          closestAgent = agent;
 0135          minDistance = sensorOutput.Position.Range;
 0136        }
 0137      }
 0138    }
 0139    return closestAgent?.Agent;
 6520140  }
 141
 142  // If the threat collides with the ground or another agent, it will be terminated. It is possible
 143  // for a threat to collide with another threat or with a non-pursuing interceptor. Interceptors
 144  // will handle colliding with a threat.
 0145  private void OnTriggerEnter(Collider other) {
 0146    if (CheckGroundCollision(other)) {
 0147      OnDestroyed?.Invoke(this);
 0148      Terminate();
 0149    }
 150
 0151    IAgent otherAgent = other.gameObject.GetComponentInParent<IAgent>();
 0152    if (ShouldIgnoreCollision(otherAgent)) {
 0153      return;
 154    }
 155    // Check if the collision is with another threat or with the intended target.
 0156    if (otherAgent is IThreat) {
 0157      OnDestroyed?.Invoke(this);
 0158      Terminate();
 0159    } else if (HierarchicalAgent.Target is HierarchicalAgent targetAgent &&
 0160               otherAgent == targetAgent.Agent) {
 0161      OnHit?.Invoke(this);
 0162      Terminate();
 0163    }
 0164  }
 165}