using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Linq; using Invector.vCharacterController.AI.FSMBehaviour; using Beyond; using System; using Invector; // Required for vDamage namespace Beyond { public class AutoTargetting : MonoBehaviour { #region Fields & Properties [Header("Targeting Parameters")] [Tooltip("The maximum distance to highlight a potential target.")] public float maxTargetingDistance = 20f; [Tooltip("The distance within which the system will automatically lock on to the highlighted target.")] public float autoLockOnDistance = 15f; [Tooltip("The distance at which a locked-on target will be automatically unlocked.")] public float unlockDistanceThreshold = 25f; public float targetingInterval = 0.25f; public float targetingAngleThreshold = 90f; [Header("Rotation Parameters")] public float playerRotationSpeed = 10f; [Header("Visuals")] public string materialHighlightPropertyName = "_FresnelColor"; [ColorUsage(true, true)] public Color highlightColor = Color.white; [ColorUsage(true, true)] public Color deselectHighlightColor = Color.black; public float highlightFadeDuration = 0.3f; public bool preferSkinnedMeshRenderer = true; [Header("Lock-On Integration")] public bool autoLockSelectedTarget = false; public bool alwaysLockOnInCombat = true; public bLockOn targetLockSystem; public float manualSwitchCooldownDuration = 0.75f; public vFSMBehaviourController CurrentTarget { get; private set; } public event Action OnTargetSelected; public event Action OnTargetDeselected; private GameStateManager _gameStateManager; private Coroutine _targetingLoopCoroutine; private Dictionary _originalMaterialColors = new Dictionary(); private Dictionary _materialToFadeCoroutineMap = new Dictionary(); private Transform _playerTransform; private bThirdPersonController _playerController; private bool _manualSwitchCooldownActive = false; private float _manualSwitchCooldownTimer = 0f; #endregion #region Unity Methods void Start() { if (Player.Instance == null) { Debug.LogError("AutoTargetting: Player.Instance is not available at Start!"); enabled = false; return; } _playerTransform = Player.Instance.transform; _playerController = Player.Instance.GetComponent(); if (_playerController == null) { Debug.LogError("AutoTargetting: Could not find bThirdPersonController on Player.Instance!"); } else { // Subscribe to Player Death _playerController.onDead.AddListener(OnPlayerDead); } _gameStateManager = GameStateManager.Instance; if (_gameStateManager != null) { _gameStateManager.m_OnStateChanged.AddListener(HandleGameStateChanged); // Initial check, but avoid running if dead if(_playerController != null && _playerController.currentHealth > 0) { HandleGameStateChanged(_gameStateManager.CurrentState); } } else { Debug.LogError("AutoTargetting: GameStateManager.Instance not found!"); enabled = false; return; } if (targetLockSystem == null) { targetLockSystem = Player.Instance.GetComponentInChildren(true); } if (targetLockSystem != null) { targetLockSystem.onLockOnTarget.AddListener(HandleLockOnSystemTargetChanged); targetLockSystem.onUnLockOnTarget.AddListener(HandleLockOnSystemTargetUnlocked); } } void OnDestroy() { if (_gameStateManager != null) _gameStateManager.m_OnStateChanged.RemoveListener(HandleGameStateChanged); // Unsubscribe from Player Death if (_playerController != null) { _playerController.onDead.RemoveListener(OnPlayerDead); } StopAndClearAllFadeCoroutines(); if (_targetingLoopCoroutine != null) StopCoroutine(_targetingLoopCoroutine); if (targetLockSystem != null) { targetLockSystem.onLockOnTarget.RemoveListener(HandleLockOnSystemTargetChanged); targetLockSystem.onUnLockOnTarget.RemoveListener(HandleLockOnSystemTargetUnlocked); } } #endregion #region Core Logic private void OnPlayerDead(GameObject deadObject) { // Immediately clear targets and stop the loop when player dies ClearTarget(false); if (_targetingLoopCoroutine != null) { StopCoroutine(_targetingLoopCoroutine); _targetingLoopCoroutine = null; } if (targetLockSystem != null) { targetLockSystem.SetLockOn(false); } } public void ResetSystem() { ClearTarget(false); _manualSwitchCooldownActive = false; _manualSwitchCooldownTimer = 0f; // Restart the loop if we are in combat if (_gameStateManager != null && _gameStateManager.CurrentState == GameStateManager.State.COMBAT) { if (_targetingLoopCoroutine == null) _targetingLoopCoroutine = StartCoroutine(TargetingLoop()); } } private void StopAndClearAllFadeCoroutines() { foreach (var pair in _materialToFadeCoroutineMap) { if (pair.Value != null) { StopCoroutine(pair.Value); } } _materialToFadeCoroutineMap.Clear(); } private void HandleGameStateChanged(GameStateManager.State newState) { // Don't start loops if player is dead if (_playerController != null && _playerController.currentHealth <= 0) return; if (newState == GameStateManager.State.COMBAT) { if (_targetingLoopCoroutine == null) _targetingLoopCoroutine = StartCoroutine(TargetingLoop()); } else { if (_targetingLoopCoroutine != null) { StopCoroutine(_targetingLoopCoroutine); _targetingLoopCoroutine = null; } if (CurrentTarget != null) SetNewTarget(null, true); _manualSwitchCooldownActive = false; } } private IEnumerator TargetingLoop() { while (true) { if (_playerController != null && _playerController.currentHealth <= 0) { yield break; } if (_manualSwitchCooldownActive) { _manualSwitchCooldownTimer -= targetingInterval; if (_manualSwitchCooldownTimer <= 0) _manualSwitchCooldownActive = false; } if (!_manualSwitchCooldownActive) { if (_playerTransform == null && Player.Instance != null) _playerTransform = Player.Instance.transform; if (_playerTransform != null) UpdateTarget(); } yield return new WaitForSeconds(targetingInterval); } } private void UpdateTarget() { if (_playerTransform == null || _gameStateManager == null || _manualSwitchCooldownActive) return; vFSMBehaviourController bestCandidate = null; float minDistanceSqr = maxTargetingDistance * maxTargetingDistance; HashSet combatControllers = _gameStateManager.GetActiveCombatcontrollers(); if (combatControllers != null && combatControllers.Count > 0) { foreach (var controller in combatControllers) { if (controller == null || !controller.gameObject.activeInHierarchy || controller.aiController.currentHealth <= 0) continue; if (!IsTargetInAngle(_playerTransform, controller, targetingAngleThreshold)) continue; float distSqr = (controller.transform.position - _playerTransform.position).sqrMagnitude; if (distSqr <= minDistanceSqr) { minDistanceSqr = distSqr; bestCandidate = controller; } } } if (CurrentTarget != bestCandidate) { SetNewTarget(bestCandidate); } UpdateLockOnState(); } private void UpdateLockOnState() { if (targetLockSystem == null || _playerTransform == null) return; bool shouldBeLocked = false; if (CurrentTarget != null && (autoLockSelectedTarget || alwaysLockOnInCombat)) { float distanceToTarget = Vector3.Distance(_playerTransform.position, CurrentTarget.transform.position); if (targetLockSystem.isLockingOn) { shouldBeLocked = distanceToTarget <= unlockDistanceThreshold; } else { shouldBeLocked = distanceToTarget <= autoLockOnDistance; } } Transform desiredLockTarget = shouldBeLocked ? CurrentTarget.transform : null; targetLockSystem.ManuallySetLockOnTarget(desiredLockTarget, shouldBeLocked); if (alwaysLockOnInCombat && desiredLockTarget != null && !targetLockSystem.isLockingOn) { targetLockSystem.SetLockOn(true); } } private void SetNewTarget(vFSMBehaviourController newTarget, bool forceLockSystemUpdate = false) { if (CurrentTarget == newTarget) return; if (CurrentTarget != null) { ApplyHighlight(CurrentTarget, false); OnTargetDeselected?.Invoke(CurrentTarget); } CurrentTarget = newTarget; if (CurrentTarget != null) { ApplyHighlight(CurrentTarget, true); OnTargetSelected?.Invoke(CurrentTarget); } } public void ExecuteRotationTowardsCurrentTarget(float deltaTime) { if (_playerController != null && !_playerController.enabled) return; if (CurrentTarget == null || _playerTransform == null) return; Vector3 directionToTarget = CurrentTarget.transform.position - _playerTransform.position; directionToTarget.y = 0f; if (directionToTarget.sqrMagnitude < 0.0001f) return; Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized); _playerTransform.rotation = Quaternion.Slerp(_playerTransform.rotation, targetRotation, deltaTime * playerRotationSpeed); } #endregion #region Lock-On System Sync private void HandleLockOnSystemTargetChanged(Transform lockedTargetTransform) { if (lockedTargetTransform == null || targetLockSystem == null) return; _manualSwitchCooldownActive = true; _manualSwitchCooldownTimer = manualSwitchCooldownDuration; vFSMBehaviourController fsmTarget = lockedTargetTransform.GetComponentInParent(); if (fsmTarget == null) fsmTarget = lockedTargetTransform.GetComponentInChildren(true); if (fsmTarget != null && CurrentTarget != fsmTarget) { SetNewTarget(fsmTarget, true); } else if (fsmTarget == null && CurrentTarget != null) { SetNewTarget(null, true); } } private void HandleLockOnSystemTargetUnlocked(Transform previouslyLockedTargetTransform) { if (targetLockSystem == null) return; _manualSwitchCooldownActive = true; _manualSwitchCooldownTimer = manualSwitchCooldownDuration; } #endregion #region Helper Methods // --- Restored Method --- public float GetCurrentTargetHealth() { if (CurrentTarget != null && CurrentTarget.aiController != null) { return CurrentTarget.aiController.currentHealth; } return -1f; } public bool IsTargetInAngle(Transform sourceTransform, vFSMBehaviourController targetAI, float angleThreshold) { if (targetAI == null || sourceTransform == null) return false; Vector3 directionToTarget = (targetAI.transform.position - sourceTransform.position); directionToTarget.y = 0; if (directionToTarget.sqrMagnitude < 0.0001f) return true; return Vector3.Angle(sourceTransform.forward, directionToTarget.normalized) <= angleThreshold; } private bool IsTargetValid(vFSMBehaviourController target) { if (target == null || !target.gameObject.activeInHierarchy || target.aiController.currentHealth <= 0) return false; if (_playerTransform == null) return false; if (!IsTargetInAngle(_playerTransform, target, targetingAngleThreshold)) return false; float distSqr = (target.transform.position - _playerTransform.position).sqrMagnitude; return distSqr <= (maxTargetingDistance * maxTargetingDistance); } public void ClearTarget(bool findNewOneImmediately) { if (targetLockSystem != null) { targetLockSystem.SetLockOn(false); } SetNewTarget(null); if (findNewOneImmediately && _gameStateManager != null && _gameStateManager.CurrentState == GameStateManager.State.COMBAT) { if (_playerController != null && _playerController.currentHealth > 0 && !_manualSwitchCooldownActive) { UpdateTarget(); } } } #endregion #region Visuals private Renderer[] GetTargetRenderers(vFSMBehaviourController targetController) { if (targetController == null) return new Renderer[0]; return targetController.GetComponentsInChildren(true).Distinct().ToArray(); } private void ApplyHighlight(vFSMBehaviourController targetController, bool fadeIn) { if (targetController == null || string.IsNullOrEmpty(materialHighlightPropertyName)) return; Renderer[] renderers = GetTargetRenderers(targetController); foreach (Renderer rend in renderers) { if (rend == null) continue; foreach (Material mat in rend.materials) { if (mat == null || !mat.HasProperty(materialHighlightPropertyName)) continue; if (_materialToFadeCoroutineMap.TryGetValue(mat, out Coroutine existingCoroutine) && existingCoroutine != null) StopCoroutine(existingCoroutine); Color currentColor = mat.GetColor(materialHighlightPropertyName); Color targetColor = fadeIn ? highlightColor : deselectHighlightColor; Coroutine newFadeCoroutine = StartCoroutine(FadeMaterialPropertyCoroutine(mat, currentColor, targetColor, highlightFadeDuration)); _materialToFadeCoroutineMap[mat] = newFadeCoroutine; } } } private IEnumerator FadeMaterialPropertyCoroutine(Material material, Color fromValue, Color toValue, float duration) { float timer = 0f; while (timer < duration) { if (material == null) yield break; timer += Time.deltaTime; material.SetColor(materialHighlightPropertyName, Color.Lerp(fromValue, toValue, Mathf.Clamp01(timer / duration))); yield return null; } if (material != null) material.SetColor(materialHighlightPropertyName, toValue); } #endregion } }