using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Linq; // Required for List.AddRange and ToArray using Invector.vCharacterController.AI.FSMBehaviour; // For vFSMBehaviourController using Beyond; // For GameStateManager and Player (ensure Player.cs is in this namespace or adjust) using System; // For Action public class AutoTargetting : MonoBehaviour { [Header("Targeting Parameters")] [Tooltip("Maximum distance AutoTargetting will consider an enemy for selection.")] public float maxTargetingDistance = 20f; [Tooltip("How often (in seconds) to re-evaluate for a new target during combat.")] public float targetingInterval = 0.25f; [Tooltip("Maximum angle (in degrees from player's forward) within which an enemy can be auto-targeted.")] public float targetingAngleThreshold = 90f; [Header("Rotation Parameters")] [Tooltip("Speed at which the player rotates towards the current target when rotation is explicitly called (e.g., by MagicAttacks).")] public float playerRotationSpeed = 10f; // This will be used by MagicAttacks [Header("Visuals")] [Tooltip("Name of the material color property to animate for Fresnel effect.")] public string materialHighlightPropertyName = "_FresnelColor"; [Tooltip("HDR Color to use for Fresnel highlight when a target is selected (fade-in target).")] [ColorUsage(true, true)] public Color highlightColor = Color.white; [Tooltip("HDR Color to use for Fresnel when a target is deselected (fade-out target).")] [ColorUsage(true, true)] public Color deselectHighlightColor = Color.black; [Tooltip("Duration of the fade in/out animation for the highlight.")] public float highlightFadeDuration = 0.3f; [Tooltip("If true, was previously used to prefer SkinnedMeshRenderer. Now GetTargetRenderers collects both SkinnedMeshRenderers and MeshRenderers regardless of this flag. This flag might be repurposed or removed in the future.")] public bool preferSkinnedMeshRenderer = true; // Note: Its effect on GetTargetRenderers is changed. 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; void Start() { if (Player.Instance == null) { Debug.LogError("AutoTargetting: Player.Instance is not available at Start! Ensure Player script with static Instance exists and runs before AutoTargetting."); enabled = false; return; } _playerTransform = Player.Instance.transform; _gameStateManager = GameStateManager.Instance; if (_gameStateManager != null) { _gameStateManager.m_OnStateChanged.AddListener(HandleGameStateChanged); HandleGameStateChanged(_gameStateManager.CurrentState); } else { Debug.LogError("AutoTargetting: GameStateManager.Instance not found! Disabling script."); enabled = false; } } void OnDestroy() { if (_gameStateManager != null) { _gameStateManager.m_OnStateChanged.RemoveListener(HandleGameStateChanged); } StopAndClearAllFadeCoroutines(); if (_targetingLoopCoroutine != null) { StopCoroutine(_targetingLoopCoroutine); _targetingLoopCoroutine = null; } } private void StopAndClearAllFadeCoroutines() { foreach (var pair in _materialToFadeCoroutineMap) { if (pair.Value != null) StopCoroutine(pair.Value); } _materialToFadeCoroutineMap.Clear(); } private void HandleGameStateChanged(GameStateManager.State newState) { if (newState == GameStateManager.State.COMBAT) { if (_targetingLoopCoroutine == null) { _targetingLoopCoroutine = StartCoroutine(TargetingLoop()); } } else { if (_targetingLoopCoroutine != null) { StopCoroutine(_targetingLoopCoroutine); _targetingLoopCoroutine = null; } if (CurrentTarget != null) { SetNewTarget(null); } } } private IEnumerator TargetingLoop() { while (true) { if (_playerTransform == null && Player.Instance != null) { _playerTransform = Player.Instance.transform; } if (_playerTransform != null) { UpdateTarget(); } yield return new WaitForSeconds(targetingInterval); } } 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; directionToTarget.Normalize(); float angle = Vector3.Angle(sourceTransform.forward, directionToTarget); return angle <= angleThreshold; } private void UpdateTarget() { if (_playerTransform == null || _gameStateManager == null) return; vFSMBehaviourController bestCandidate = null; float minDistanceSqr = maxTargetingDistance * maxTargetingDistance; HashSet combatControllers = _gameStateManager.GetActiveCombatcontrollers(); if (combatControllers == null || combatControllers.Count == 0) { if (CurrentTarget != null) SetNewTarget(null); return; } 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); } else if (CurrentTarget != null && !IsTargetValid(CurrentTarget)) { SetNewTarget(null); } } 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; } private void SetNewTarget(vFSMBehaviourController newTarget) { 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 (CurrentTarget == null || _playerTransform == null) { return; } Vector3 directionToTarget = CurrentTarget.transform.position - _playerTransform.position; directionToTarget.y = 0f; if (directionToTarget.sqrMagnitude < 0.0001f) return; directionToTarget.Normalize(); Quaternion targetRotation = Quaternion.LookRotation(directionToTarget); _playerTransform.rotation = Quaternion.Lerp(_playerTransform.rotation, targetRotation, deltaTime * playerRotationSpeed); } public float GetCurrentTargetHealth() { if (CurrentTarget != null && CurrentTarget.aiController != null) { return CurrentTarget.aiController.currentHealth; } return -1f; } public void ClearTarget(bool findNewOneImmediately) { SetNewTarget(null); if (findNewOneImmediately && _gameStateManager != null && _gameStateManager.CurrentState == GameStateManager.State.COMBAT) { UpdateTarget(); } } // *** MODIFIED GetTargetRenderers METHOD *** private Renderer[] GetTargetRenderers(vFSMBehaviourController targetController) { if (targetController == null) return new Renderer[0]; List collectedRenderers = new List(); // Collect all SkinnedMeshRenderers SkinnedMeshRenderer[] smrs = targetController.GetComponentsInChildren(true); if (smrs != null && smrs.Length > 0) { collectedRenderers.AddRange(smrs); } // Collect all MeshRenderers MeshRenderer[] mrs = targetController.GetComponentsInChildren(true); if (mrs != null && mrs.Length > 0) { collectedRenderers.AddRange(mrs); } // If no specific SMRs or MRs were found, fall back to all Renderers (optional, but good for safety) // Or, if the goal is *only* SMRs and MRs, this fallback can be removed. // For now, let's assume we want to highlight *something* if possible. // If you strictly want ONLY SMRs and MRs, and nothing else, remove this 'if' block. if (collectedRenderers.Count == 0) { Renderer[] allRenderers = targetController.GetComponentsInChildren(true); if (allRenderers != null && allRenderers.Length > 0) { collectedRenderers.AddRange(allRenderers); } } // The preferSkinnedMeshRenderer flag is not directly used here for filtering types anymore. // It could potentially be used for ordering or other logic if needed in the future. return collectedRenderers.Distinct().ToArray(); // Distinct() to avoid duplicates if an object somehow has multiple relevant renderer types } 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; if (fadeIn) { if (!_originalMaterialColors.ContainsKey(mat) || (_originalMaterialColors[mat] != currentColor && currentColor != deselectHighlightColor && currentColor != highlightColor) ) { _originalMaterialColors[mat] = currentColor; } } 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; float progress = Mathf.Clamp01(timer / duration); material.SetColor(materialHighlightPropertyName, Color.Lerp(fromValue, toValue, progress)); yield return null; } if (material != null) { material.SetColor(materialHighlightPropertyName, toValue); } } }