using System; using System.Collections.Generic; using UnityEngine; namespace ProjectI.Enemies { public sealed partial class EnemyPositioning { private readonly Dictionary alertedAt = new Dictionary(); private readonly List invalidAlerts = new List(); private double nextAlertAttempt; private EnemyMovementContext alertContext; private EnemyActionToken alertToken; private void CancelAlert() { if (alertContext == null) return; movement?.Cancel(alertToken); alertContext = null; alertToken = default; } private void TickAlert(double now) { if (alertContext == null) return; var evidence = alertContext.Evidence.Value; if (!movement.HasObservation || !movement.ObservationToken.Equals(alertToken) || !alertContext.IsCurrent(now) || perception.TryVerifyPersonalSightServer(evidence.Target, now, out _) || Facing(transform.rotation * perception.LocalEyeRotation * Vector3.forward, evidence.Position - (transform.position + transform.rotation * perception.LocalEye), 5)) CancelAlert(); } private bool AlertAttackActive() { var attack = GetComponent(); return attack != null && (attack.Phase == EnemyBoltExecution.AttackPhase.Preparation || attack.Phase == EnemyBoltExecution.AttackPhase.Committed || attack.Phase == EnemyBoltExecution.AttackPhase.BurstSpacing); } internal bool AlertCanContinueServer(EnemyObservation evidence, double now) => actor != null && actor.IsValid && actor.ReadyForActionsServer && !actor.Status.Rooted && !actor.Status.Stunned && !AlertAttackActive() && perception.TryGetSelectedTarget(now, out var selected) && selected.Equals(evidence.Target); private bool TryBeginAlert(double now) { invalidAlerts.Clear(); foreach (var pair in alertedAt) if (!perception.TryGetSnapshot(pair.Key, now, out _)) invalidAlerts.Add(pair.Key); foreach (var key in invalidAlerts) alertedAt.Remove(key); if (now < nextAlertAttempt || !actor.ReadyForActionsServer || actor.Status.Rooted || actor.Status.Stunned || HasAcceptedTravel || movement.StraightMoveActive || movement.HasPendingAction || candidates != null || investigationPlanner?.PendingContext != null || ActiveInvestigation != null && !ActiveInvestigation.Terminal) return false; var attack = GetComponent(); if (attack == null || !attack.isActiveAndEnabled || AlertAttackActive() || !perception.TryGetSelectedTarget(now, out var target) || perception.TryVerifyPersonalSightServer(target, now, out _) || !perception.TryCaptureAlertServer(target, now, out var evidence, out double evidenceUntil) || alertedAt.TryGetValue(target, out double seenAt) && evidence.ObservedAt <= seenAt || !alertedAt.ContainsKey(target) && alertedAt.Count >= 32 || Facing(transform.rotation * perception.LocalEyeRotation * Vector3.forward, evidence.Position - (transform.position + transform.rotation * perception.LocalEye), 5)) return false; // Orientation owns no route, area or pursuit allowance; all execution remains in the movement owner. nextAlertAttempt = now + PlanningInterval; var request = movement.IssueTokenServer(); double deadline = Math.Min(evidenceUntil, now + limits.Seconds); var authority = new EnemyMovementContext(actor, request, EnemyMovementContextKind.AlertOrientation, evidence, deadline); if (!movement.TryObserve(request, evidence.Position, deadline, authority)) { movement.Cancel(request); return false; } alertedAt[target] = evidence.ObservedAt; alertContext = authority; alertToken = request; return true; } } }