Files
beyond/Assets/Scripts/Characters/AutoTargetting.cs

438 lines
17 KiB
C#

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<vFSMBehaviourController> OnTargetSelected;
public event Action<vFSMBehaviourController> OnTargetDeselected;
private GameStateManager _gameStateManager;
private Coroutine _targetingLoopCoroutine;
private Dictionary<Material, Color> _originalMaterialColors = new Dictionary<Material, Color>();
private Dictionary<Material, Coroutine> _materialToFadeCoroutineMap = new Dictionary<Material, Coroutine>();
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<bThirdPersonController>();
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<bLockOn>(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<vFSMBehaviourController> 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<vFSMBehaviourController>();
if (fsmTarget == null) fsmTarget = lockedTargetTransform.GetComponentInChildren<vFSMBehaviourController>(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<Renderer>(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
}
}