using System; using BumpCombat.Enemies; using UnityEngine; namespace BumpCombat.Defense { [DisallowMultipleComponent] public sealed class DefenseRouteAgent : MonoBehaviour { private EnemyController enemy; private Vector2[] waypoints = Array.Empty(); private Action arrivalHandler; private Transform player; private BumpCombat.Player.PlayerHealth playerHealth; private float routeSpeed; private float playerEngagementRange; private float postAttackProgressSeconds; private float postAttackProgressRemaining; private bool wasAttackSequenceInProgress; private bool configured; private bool stationaryTowerTestDummy; private Vector2 stationaryTowerTestDummyAnchor; private int waypointIndex; public EnemyController Enemy => enemy; public int Lane { get; private set; } public int LifetimeId => enemy != null ? enemy.SpawnLifetimeIdentity : -1; public DefenseTowerKind ImmuneTowerKind { get; private set; } public Vector2 AnchorPosition => waypoints.Length > 0 ? waypoints[0] : transform.position; public bool HasArrived { get; private set; } public void Configure( EnemyController configuredEnemy, int lane, Vector2[] route, float speed, float engagementRange, float postAttackProgress, DefenseTowerKind immuneTowerKind, Action onArrived) { enemy = configuredEnemy; Lane = lane; waypoints = route ?? Array.Empty(); routeSpeed = Mathf.Max(0.1f, speed); playerEngagementRange = Mathf.Max(0.1f, engagementRange); postAttackProgressSeconds = Mathf.Max(0f, postAttackProgress); postAttackProgressRemaining = 0f; wasAttackSequenceInProgress = false; ImmuneTowerKind = immuneTowerKind; arrivalHandler = onArrived; waypointIndex = 0; configured = enemy != null && waypoints.Length > 0; HasArrived = false; player = FindAnyObjectByType()?.transform; playerHealth = player != null ? player.GetComponent() : null; if (configured) { enemy.TryReposition(waypoints[0]); } } public void ConfigureTowerTestDummy(EnemyController configuredEnemy, DefenseTowerKind immuneTowerKind) { enemy = configuredEnemy; Lane = -1; waypoints = Array.Empty(); routeSpeed = 0f; playerEngagementRange = 0f; postAttackProgressSeconds = 0f; ImmuneTowerKind = immuneTowerKind; arrivalHandler = null; waypointIndex = 0; configured = enemy != null; stationaryTowerTestDummy = configured; stationaryTowerTestDummyAnchor = configuredEnemy != null ? configuredEnemy.transform.position : Vector2.zero; HasArrived = false; player = FindAnyObjectByType()?.transform; playerHealth = player != null ? player.GetComponent() : null; } public bool TryDriveFixedUpdate(float deltaTime) { if (stationaryTowerTestDummy) { if (enemy == null || enemy.IsDead) { return true; } if (enemy.State == EnemyState.Spawn) { return false; } enemy.SetDefenseCombatTarget(null); enemy.TryReposition(stationaryTowerTestDummyAnchor); return true; } if (!configured || HasArrived || enemy == null || enemy.IsDead) { return false; } if (enemy.State == EnemyState.Spawn) { return false; } bool attackSequenceActive = enemy.IsAttackSequenceInProgress; if (attackSequenceActive) { wasAttackSequenceInProgress = true; return false; } if (wasAttackSequenceInProgress) { wasAttackSequenceInProgress = false; postAttackProgressRemaining = postAttackProgressSeconds; } if (enemy.IsStunned) { return false; } DefensePrototypeController defense = DefensePrototypeController.Instance; DefenseSummonUnit summonTarget = defense != null ? defense.FindNearestSummonTarget(transform.position, Mathf.Max(playerEngagementRange, defense.Tuning.SummonAreaRadius)) : null; enemy.SetDefenseCombatTarget(summonTarget); if (summonTarget != null) { enemy.Alert(); return false; } postAttackProgressRemaining = Mathf.Max( 0f, postAttackProgressRemaining - Mathf.Max(0f, deltaTime)); if (player == null) { player = FindAnyObjectByType()?.transform; playerHealth = player != null ? player.GetComponent() : null; } if (postAttackProgressRemaining <= 0f && player != null && playerHealth != null && playerHealth.CurrentHealth > 0f && (player.position - transform.position).sqrMagnitude <= playerEngagementRange * playerEngagementRange) { enemy.Alert(); return false; } if (waypointIndex >= waypoints.Length) { CompleteArrival(); return true; } Vector2 current = transform.position; Vector2 target = waypoints[waypointIndex]; Vector2 next = Vector2.MoveTowards( current, target, routeSpeed * enemy.MovementSpeedMultiplier * Mathf.Max(0f, deltaTime)); if (!enemy.TryReposition(next)) { return false; } Vector2 movementDirection = next - current; if (movementDirection.sqrMagnitude > 0.000001f) { enemy.SetFacingDirection(movementDirection); } Animator animator = enemy.GetComponent(); animator?.SetBool("IsMoving", (next - current).sqrMagnitude > 0.000001f); if ((target - next).sqrMagnitude <= 0.01f) { waypointIndex++; if (waypointIndex >= waypoints.Length) { CompleteArrival(); } } return true; } public bool TryTakeTowerDamage( DefenseTowerKind sourceKind, float requestedDamage, float distanceToPlayer, float sightRadius, float outsideSightEfficiency, Vector2 sourcePosition) { if (!configured || HasArrived || enemy == null || enemy.IsDead || DefensePrototypeRules.IsTowerImmune( sourceKind, ImmuneTowerKind)) { return false; } float efficiency = DefensePrototypeRules.GetTowerEfficiency( distanceToPlayer, sightRadius, 1f, outsideSightEfficiency); Vector2 direction = ((Vector2)transform.position - sourcePosition).normalized; return enemy.TryTakeDamage( Mathf.Max(0f, requestedDamage) * efficiency, direction, 0f, false, false, EnemyDamageSource.Tower); } private void CompleteArrival() { if (HasArrived) { return; } HasArrived = true; arrivalHandler?.Invoke(this); } } }