< Summary

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

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
LookupPowerTable(...)0%2100%
HandleIntercept()0%6200%
Start()0%30500%
FixedUpdate()0%56700%
UpdateAgentConfig()0%1321100%
FindClosestPursuer()0%1101000%
OnTriggerEnter(...)0%90900%

File(s)

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

#LineLine coverage
 1using System.Collections.Generic;
 2using System.Linq;
 3using UnityEngine;
 4
 5// Base implementation of a threat.
 6public abstract class ThreatBase : AgentBase, IThreat {
 7  public event ThreatHitMissEventHandler OnHit;
 8  public event ThreatHitMissEventHandler OnMiss;
 9
 10  // Speed difference threshold for applying forward acceleration.
 11  private const float _speedErrorThreshold = 1f;
 12
 13  // Power table map from the power to the speed.
 14  private Dictionary<Configs.Power, float> _powerTable;
 15
 16  // The attack behavior determines how the threat navigates towards the asset.
 017  public IAttackBehavior AttackBehavior { get; set; }
 18
 19  // The evasion handles how the threat behaves in the vicinity of a pursuing interceptor.
 020  public IEvasion Evasion { get; set; }
 21
 22  public IReadOnlyDictionary<Configs.Power, float> PowerTable {
 023    get {
 024      if (_powerTable == null) {
 025        _powerTable =
 026            StaticConfig.PowerTable.ToDictionary(entry => entry.Power, entry => entry.Speed);
 027      }
 028      return _powerTable;
 029    }
 30  }
 31
 032  public float LookupPowerTable(Configs.Power power) {
 033    PowerTable.TryGetValue(power, out float speed);
 034    return speed;
 035  }
 36
 037  public void HandleIntercept() {
 038    OnMiss?.Invoke(this);
 039    Terminate();
 040  }
 41
 042  protected override void Start() {
 043    base.Start();
 44
 45    // The threat should target the nearest launcher.
 046    IHierarchical target = null;
 047    var launchers = IADS.Instance.Launchers;
 048    if (launchers.Count == 0) {
 049      target = new FixedHierarchical(position: Vector3.zero);
 050    } else {
 051      float minDistanceSqr = Mathf.Infinity;
 052      foreach (var launcher in launchers) {
 053        float distanceSqr = (launcher.Position - Position).sqrMagnitude;
 054        if (distanceSqr < minDistanceSqr) {
 055          minDistanceSqr = distanceSqr;
 056          target = launcher;
 057        }
 058      }
 059    }
 060    HierarchicalAgent.Target = target;
 061  }
 62
 063  protected override void FixedUpdate() {
 064    base.FixedUpdate();
 65
 066    float desiredSpeed = 0f;
 67    // Check whether the threat should evade any pursuer.
 068    IAgent closestPursuer = FindClosestPursuer();
 069    if (Evasion != null && closestPursuer != null && Evasion.ShouldEvade(closestPursuer)) {
 070      AccelerationInput = Evasion.Evade(closestPursuer);
 071      desiredSpeed = LookupPowerTable(Configs.Power.Max);
 072    } else {
 73      // Follow the attack behavior.
 074      (Vector3 waypoint, Configs.Power waypointPower) =
 75          AttackBehavior.GetNextWaypoint(TargetModel.Position);
 076      AccelerationInput = Controller?.Plan(waypoint) ?? Vector3.zero;
 077      desiredSpeed = LookupPowerTable(waypointPower);
 078    }
 79
 80    // Limit the forward acceleration according to the desired speed.
 081    float speedError = desiredSpeed - Speed;
 082    Vector3 forwardAccelerationInput = Vector3.Project(AccelerationInput, Forward);
 083    Vector3 normalAccelerationInput = Vector3.ProjectOnPlane(AccelerationInput, Forward);
 084    if (Mathf.Abs(speedError) < _speedErrorThreshold) {
 085      AccelerationInput = normalAccelerationInput;
 086    } else {
 087      float speedFactor = Mathf.Clamp01(Mathf.Abs(speedError) / _speedErrorThreshold);
 088      AccelerationInput =
 89          normalAccelerationInput + forwardAccelerationInput * Mathf.Sign(speedError) * speedFactor;
 090    }
 91
 092    Acceleration = Movement?.Act(AccelerationInput) ?? Vector3.zero;
 093    _rigidbody.AddForce(Acceleration, ForceMode.Acceleration);
 094  }
 95
 096  protected override void UpdateAgentConfig() {
 097    base.UpdateAgentConfig();
 98
 99    // Set the attack behavior.
 0100    Configs.AttackBehaviorConfig attackBehaviorConfig =
 101        ConfigLoader.LoadAttackBehaviorConfig(AgentConfig.AttackBehaviorConfigFile ?? "");
 0102    switch (attackBehaviorConfig?.Type) {
 0103      case Configs.AttackType.DirectAttack: {
 0104        AttackBehavior = new DirectAttackBehavior(this, attackBehaviorConfig);
 0105        break;
 106      }
 107      case Configs.AttackType.PreplannedAttack:
 0108      case Configs.AttackType.SlalomAttack: {
 0109        Debug.LogError($"Attack behavior type {attackBehaviorConfig?.Type} is unimplemented.");
 0110        break;
 111      }
 0112      default: {
 0113        Debug.LogError($"Attack behavior type {attackBehaviorConfig?.Type} not found.");
 0114        break;
 115      }
 116    }
 117
 118    // Set the evasion.
 0119    Evasion = new OrthogonalEvasion(this);
 0120  }
 121
 0122  private IAgent FindClosestPursuer() {
 0123    if (HierarchicalAgent == null || !HierarchicalAgent.ActivePursuers.Any() || Sensor == null) {
 0124      return null;
 125    }
 126
 0127    HierarchicalAgent closestAgent = null;
 0128    float minDistance = float.MaxValue;
 0129    foreach (var pursuer in HierarchicalAgent.ActivePursuers) {
 0130      if (pursuer is HierarchicalAgent agent) {
 0131        SensorOutput sensorOutput = Sensor.Sense(agent);
 0132        if (sensorOutput.Position.Range < minDistance) {
 0133          closestAgent = agent;
 0134          minDistance = sensorOutput.Position.Range;
 0135        }
 0136      }
 0137    }
 0138    return closestAgent?.Agent;
 0139  }
 140
 141  // If the threat collides with the ground or another agent, it will be terminated. It is possible
 142  // for a threat to collide with another threat or with a non-pursuing interceptor. Interceptors
 143  // will handle colliding with a threat.
 0144  private void OnTriggerEnter(Collider other) {
 0145    if (CheckFloorCollision(other)) {
 0146      OnMiss?.Invoke(this);
 0147      Terminate();
 0148    }
 149
 0150    IAgent otherAgent = other.gameObject.GetComponentInParent<IAgent>();
 0151    if (ShouldIgnoreCollision(otherAgent)) {
 0152      return;
 153    }
 154    // Check if the collision is with another threat or with the intended target.
 0155    if (otherAgent is IThreat) {
 0156      OnMiss?.Invoke(this);
 0157      Terminate();
 0158    } else if (HierarchicalAgent.Target is HierarchicalAgent targetAgent &&
 0159               otherAgent == targetAgent.Agent) {
 0160      OnHit?.Invoke(this);
 0161      Terminate();
 0162    }
 0163  }
 164}