From 27d5c6375138968fc3088c49d58b4149cb2b3fab Mon Sep 17 00:00:00 2001 From: marcin Date: Fri, 6 Jun 2025 12:23:52 +0200 Subject: [PATCH] fix in autoweapon draw/ hide, bLock, bMeleeCombat and Input --- .../Beasiculus/Bascileus_BaseModel.prefab | 3 + Assets/Prefabs/PlayerWithUI.prefab | 3 + Assets/Scripts/Characters/AutoTargetting.cs | 736 ++++++++++-------- .../bDrawHideMeleeWeapons.cs | 27 +- Assets/Scripts/InvectorDerivatives/bLockOn.cs | 377 ++++++--- .../InvectorDerivatives/bMeleeCombatInput.cs | 420 ++++++---- ProjectSettings/EditorBuildSettings.asset | 6 + 7 files changed, 983 insertions(+), 589 deletions(-) diff --git a/Assets/Prefabs/Characters/Beasiculus/Bascileus_BaseModel.prefab b/Assets/Prefabs/Characters/Beasiculus/Bascileus_BaseModel.prefab index 82813dd93..82bf32136 100644 --- a/Assets/Prefabs/Characters/Beasiculus/Bascileus_BaseModel.prefab +++ b/Assets/Prefabs/Characters/Beasiculus/Bascileus_BaseModel.prefab @@ -106100,6 +106100,9 @@ MonoBehaviour: lastTimeTheButtonWasPressed: 0 inButtomTimer: 0 lockMeleeInput: 0 + dashRotationSpeed: 15 + dashFacingAngleThreshold: 10 + maxDashRotationTime: 0.5 --- !u!114 &9202663234309573817 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/Prefabs/PlayerWithUI.prefab b/Assets/Prefabs/PlayerWithUI.prefab index 59505beda..92814cd64 100644 --- a/Assets/Prefabs/PlayerWithUI.prefab +++ b/Assets/Prefabs/PlayerWithUI.prefab @@ -18997,6 +18997,9 @@ MonoBehaviour: deselectHighlightColor: {r: 0, g: 0, b: 0, a: 1} highlightFadeDuration: 0.3 preferSkinnedMeshRenderer: 1 + autoLockSelectedTarget: 0 + targetLockSystem: {fileID: 0} + manualSwitchCooldownDuration: 0.75 --- !u!4 &6425420852750441961 stripped Transform: m_CorrespondingSourceObject: {fileID: 5744920779938531276, guid: 60b79e23a507e0c48a94b7e3d5138383, diff --git a/Assets/Scripts/Characters/AutoTargetting.cs b/Assets/Scripts/Characters/AutoTargetting.cs index 377a0ee49..d9983dc56 100644 --- a/Assets/Scripts/Characters/AutoTargetting.cs +++ b/Assets/Scripts/Characters/AutoTargetting.cs @@ -1,359 +1,467 @@ 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 +using System.Linq; +using Invector.vCharacterController.AI.FSMBehaviour; +using Beyond; +using System; -public class AutoTargetting : MonoBehaviour +namespace Beyond { - [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() + public class AutoTargetting : MonoBehaviour { - 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; + // ... (other headers and variables) + [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; - _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; - } - } + [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; - void OnDestroy() - { - if (_gameStateManager != null) - { - _gameStateManager.m_OnStateChanged.RemoveListener(HandleGameStateChanged); - } - StopAndClearAllFadeCoroutines(); - if (_targetingLoopCoroutine != null) - { - StopCoroutine(_targetingLoopCoroutine); - _targetingLoopCoroutine = null; - } - } + [Header("Visuals")] + // ... (visual parameters) + 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; - 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) + [Header("Lock-On Integration")] + [Tooltip("If true, automatically locks onto the target selected by AutoTargetting.")] + public bool autoLockSelectedTarget = false; + [Tooltip("Reference to the bLockOn script, usually on the player. Will try to find if not set.")] + public bLockOn targetLockSystem; + [Tooltip("How long (in seconds) AutoTargetting will pause after a manual target switch before re-evaluating.")] + public float manualSwitchCooldownDuration = 0.75f; // Cooldown duration + + 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; + + // Cooldown variables + private bool _manualSwitchCooldownActive = false; + private float _manualSwitchCooldownTimer = 0f; + + void Start() { - if (_targetingLoopCoroutine == null) + // ... (Start logic mostly the same) + if (Player.Instance == null) { - _targetingLoopCoroutine = StartCoroutine(TargetingLoop()); + Debug.LogError("AutoTargetting: Player.Instance is not available at Start!"); + 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!"); + enabled = false; return; + } + + if (targetLockSystem == null) + { + if (Player.Instance != null) targetLockSystem = Player.Instance.GetComponentInChildren(true); + if (targetLockSystem == null) targetLockSystem = GetComponent(); // Fallback + if (targetLockSystem == null) + { + Debug.LogWarning("AutoTargetting: bLockOn system not found. Auto-lock and target sync will be disabled."); + autoLockSelectedTarget = false; + } + } + + if (targetLockSystem != null) + { + targetLockSystem.onLockOnTarget.AddListener(HandleLockOnSystemTargetChanged); + targetLockSystem.onUnLockOnTarget.AddListener(HandleLockOnSystemTargetUnlocked); } } - else + + void OnDestroy() { + // ... (Cleanup logic) + if (_gameStateManager != null) + { + _gameStateManager.m_OnStateChanged.RemoveListener(HandleGameStateChanged); + } + StopAndClearAllFadeCoroutines(); if (_targetingLoopCoroutine != null) { StopCoroutine(_targetingLoopCoroutine); _targetingLoopCoroutine = null; } - if (CurrentTarget != null) + + if (targetLockSystem != null) { - SetNewTarget(null); + targetLockSystem.onLockOnTarget.RemoveListener(HandleLockOnSystemTargetChanged); + targetLockSystem.onUnLockOnTarget.RemoveListener(HandleLockOnSystemTargetUnlocked); } } - } - - private IEnumerator TargetingLoop() - { - while (true) + private void StopAndClearAllFadeCoroutines() { - if (_playerTransform == null && Player.Instance != null) + foreach (var pair in _materialToFadeCoroutineMap) { - _playerTransform = Player.Instance.transform; + if (pair.Value != null) StopCoroutine(pair.Value); } + _materialToFadeCoroutineMap.Clear(); + } - if (_playerTransform != null) + + private void HandleGameStateChanged(GameStateManager.State newState) + { + if (newState == GameStateManager.State.COMBAT) { - 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) + if (_targetingLoopCoroutine == null) { - StopCoroutine(existingCoroutine); + _targetingLoopCoroutine = StartCoroutine(TargetingLoop()); } - - Color currentColor = mat.GetColor(materialHighlightPropertyName); - Color targetColor = fadeIn ? highlightColor : deselectHighlightColor; - - if (fadeIn) + } + else + { + if (_targetingLoopCoroutine != null) { - if (!_originalMaterialColors.ContainsKey(mat) || - (_originalMaterialColors[mat] != currentColor && currentColor != deselectHighlightColor && currentColor != highlightColor) ) + StopCoroutine(_targetingLoopCoroutine); + _targetingLoopCoroutine = null; + } + if (CurrentTarget != null) + { + SetNewTarget(null, true); // Force immediate update if leaving combat + } + _manualSwitchCooldownActive = false; // Reset cooldown when leaving combat + } + } + + private IEnumerator TargetingLoop() + { + while (true) + { + if (_manualSwitchCooldownActive) + { + _manualSwitchCooldownTimer -= targetingInterval; // Or Time.deltaTime if loop is faster + if (_manualSwitchCooldownTimer <= 0) { - _originalMaterialColors[mat] = currentColor; + _manualSwitchCooldownActive = false; + // Debug.Log("AutoTargetting: Manual switch cooldown ended."); } } - Coroutine newFadeCoroutine = StartCoroutine(FadeMaterialPropertyCoroutine(mat, currentColor, targetColor, highlightFadeDuration)); - _materialToFadeCoroutineMap[mat] = newFadeCoroutine; + + if (!_manualSwitchCooldownActive) // Only update target if cooldown is not active + { + if (_playerTransform == null && Player.Instance != null) + { + _playerTransform = Player.Instance.transform; + } + + if (_playerTransform != null) + { + UpdateTarget(); + } + } + yield return new WaitForSeconds(targetingInterval); } } - } - private IEnumerator FadeMaterialPropertyCoroutine(Material material, Color fromValue, Color toValue, float duration) - { - float timer = 0f; - while (timer < duration) + public bool IsTargetInAngle(Transform sourceTransform, vFSMBehaviourController targetAI, float angleThreshold) { - 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; + // ... (IsTargetInAngle logic - unchanged) + 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; } - if (material != null) + + private void UpdateTarget() { - material.SetColor(materialHighlightPropertyName, toValue); + if (_playerTransform == null || _gameStateManager == null) return; + if (_manualSwitchCooldownActive) return; // Double check, though TargetingLoop should handle it + + 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) + { + // ... (IsTargetValid logic - unchanged) + 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); + } + + + /// + /// Sets a new target for AutoTargetting. + /// + /// The new target. Can be null. + /// If true, ManuallySetLockOnTarget will be called even if newTarget is same as CurrentTarget (used for re-assertion). + private void SetNewTarget(vFSMBehaviourController newTarget, bool forceLockSystemUpdate = false) + { + if (_manualSwitchCooldownActive && !forceLockSystemUpdate) // Don't change target if cooldown is active, unless forced + { + // If cooldown is active and this SetNewTarget call is NOT from a HandleLockOnSystem... event, + // it means AutoTargeting's own loop tried to change target. We prevent this. + // If it IS from HandleLockOnSystem... it means bLockOn initiated, and we need to sync. + // The 'isManualSwitch' parameter in HandleLockOnSystemTargetChanged handles activating cooldown. + return; + } + + + if (CurrentTarget == newTarget && !forceLockSystemUpdate) + { + // If auto-lock is enabled, ensure the bLockOn system is actually locked on this target. + if (autoLockSelectedTarget && targetLockSystem != null && CurrentTarget != null) + { + if (targetLockSystem.GetCurrentLockOnTarget() != CurrentTarget.transform) + { + targetLockSystem.ManuallySetLockOnTarget(CurrentTarget.transform, true); + } + } + return; + } + + if (CurrentTarget != null) + { + ApplyHighlight(CurrentTarget, false); + OnTargetDeselected?.Invoke(CurrentTarget); + if (autoLockSelectedTarget && targetLockSystem != null && + targetLockSystem.GetCurrentLockOnTarget() == CurrentTarget.transform && + (newTarget == null || newTarget.transform != CurrentTarget.transform)) + { + targetLockSystem.ManuallySetLockOnTarget(null, false); + } + } + + CurrentTarget = newTarget; + + if (CurrentTarget != null) + { + ApplyHighlight(CurrentTarget, true); + OnTargetSelected?.Invoke(CurrentTarget); + if (autoLockSelectedTarget && targetLockSystem != null) + { + targetLockSystem.ManuallySetLockOnTarget(CurrentTarget.transform, true); + } + } + else // CurrentTarget is now null + { + // Deselection logic above handles unlocking if autoLockSelectedTarget was true + // and if bLockOn was on the CurrentTarget that just became null. + } + } + + private void HandleLockOnSystemTargetChanged(Transform lockedTargetTransform) + { + if (lockedTargetTransform == null || targetLockSystem == null) return; + + // This event means bLockOn changed target (e.g., player pressed Next/Previous) + // Activate the cooldown for AutoTargetting. + _manualSwitchCooldownActive = true; + _manualSwitchCooldownTimer = manualSwitchCooldownDuration; + // Debug.Log($"AutoTargetting: Manual switch detected via bLockOn. Cooldown activated for {manualSwitchCooldownDuration}s."); + + vFSMBehaviourController fsmTarget = lockedTargetTransform.GetComponentInParent(); + if (fsmTarget == null) fsmTarget = lockedTargetTransform.GetComponentInChildren(true); + + + if (CurrentTarget != null && CurrentTarget.transform == lockedTargetTransform) + { + // AutoTargetting was already on this target, but bLockOn re-confirmed. + // Cooldown is still active due to bLockOn's event. + return; + } + + if (fsmTarget != null) + { + // Debug.Log($"AutoTargeting: Syncing to bLockOn's new target: {fsmTarget.name}."); + // We need to update AutoTargetting's CurrentTarget to match bLockOn, + // even during cooldown, because bLockOn IS the source of truth now. + // The 'forceLockSystemUpdate = true' ensures highlights are updated correctly. + SetNewTarget(fsmTarget, true); + } + else + { + // bLockOn locked something without an FSM, or unlocked. + // If AutoTargeting had a target, it should now clear it to sync. + // Debug.LogWarning($"AutoTargeting: bLockOn locked {lockedTargetTransform.name} (no FSM) or unlocked. Clearing AutoTarget."); + SetNewTarget(null, true); + } + } + + private void HandleLockOnSystemTargetUnlocked(Transform previouslyLockedTargetTransform) + { + if (targetLockSystem == null) return; + // This event means bLockOn explicitly unlocked (e.g., player pressed Tab again). + + // If player manually unlocks, we should respect that and also stop AutoTargetting's autoLock behavior for a bit. + // Or, if autoLockSelectedTarget is false, this is just a notification. + _manualSwitchCooldownActive = true; // Treat manual unlock like a manual switch + _manualSwitchCooldownTimer = manualSwitchCooldownDuration; + // Debug.Log($"AutoTargeting: Manual unlock detected via bLockOn. Cooldown activated."); + + string targetName = previouslyLockedTargetTransform ? previouslyLockedTargetTransform.name : "an unknown target"; + // Debug.Log($"AutoTargeting: bLockOn unlocked from '{targetName}'."); + + if (CurrentTarget != null && previouslyLockedTargetTransform == CurrentTarget.transform) + { + // AutoTargeting's current target was the one that bLockOn just unlocked. + // We should clear AutoTargeting's target to be in sync. + // The cooldown will prevent immediate re-auto-targeting. + SetNewTarget(null, true); + } + // If previouslyLockedTargetTransform was something else, AutoTargeting might already be on null or a different target. + // The cooldown ensures AutoTargeting doesn't fight this unlock. + } + + + // ... (ExecuteRotationTowardsCurrentTarget, GetCurrentTargetHealth, ClearTarget, Renderer methods, Fade coroutine remain the same) + 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, true); // Force update if clearing target + if (findNewOneImmediately && _gameStateManager != null && _gameStateManager.CurrentState == GameStateManager.State.COMBAT) + { + // UpdateTarget will respect cooldown, so immediate find might be delayed + // If you need it to bypass cooldown here, more logic is needed. + // For now, let's assume standard behavior. + if (!_manualSwitchCooldownActive) UpdateTarget(); + } + } + + private Renderer[] GetTargetRenderers(vFSMBehaviourController targetController) + { + if (targetController == null) return new Renderer[0]; + List collectedRenderers = new List(); + SkinnedMeshRenderer[] smrs = targetController.GetComponentsInChildren(true); + if (smrs != null && smrs.Length > 0) collectedRenderers.AddRange(smrs); + MeshRenderer[] mrs = targetController.GetComponentsInChildren(true); + if (mrs != null && mrs.Length > 0) collectedRenderers.AddRange(mrs); + if (collectedRenderers.Count == 0) + { + Renderer[] allRenderers = targetController.GetComponentsInChildren(true); + if (allRenderers != null && allRenderers.Length > 0) collectedRenderers.AddRange(allRenderers); + } + return collectedRenderers.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; + 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); + } } } } \ No newline at end of file diff --git a/Assets/Scripts/InvectorDerivatives/bDrawHideMeleeWeapons.cs b/Assets/Scripts/InvectorDerivatives/bDrawHideMeleeWeapons.cs index ead722a55..cf361c48e 100644 --- a/Assets/Scripts/InvectorDerivatives/bDrawHideMeleeWeapons.cs +++ b/Assets/Scripts/InvectorDerivatives/bDrawHideMeleeWeapons.cs @@ -65,6 +65,27 @@ namespace Beyond BindWeaponDrawEffectsOnEquipped(); HideWeapons(true); + GameStateManager.Instance.m_OnStateChanged.AddListener(OnGameStateChanged); + } + + private void OnGameStateChanged(GameStateManager.State arg0) + { + if (weaponsHided && arg0 == GameStateManager.State.NORMAL) + { + if (debugMode) + { + Debug.Log("Returning to normal state, drawing weapons"); + } + HideWeapons(true); + } + else if (!weaponsHided && arg0 == GameStateManager.State.COMBAT) + { + if (debugMode) + { + Debug.Log("Entering combat state, hiding weapons"); + } + DrawWeapons(true); + } } private void OnDestroy() @@ -78,6 +99,10 @@ namespace Beyond itemManager.onEquipItem.RemoveListener(PlayWeaponHideEffect); itemManager.onFinishEquipItem.RemoveListener(BindNewlyEquippedWeaponDrawHideEffects); } + if (GameStateManager.Instance != null) + { + GameStateManager.Instance.m_OnStateChanged.RemoveListener(OnGameStateChanged); + } } private void BindWeaponDrawEffectsOnEquipped() @@ -256,7 +281,7 @@ namespace Beyond protected virtual bool CanHideWeapons() { - return melee && melee.meleeManager && (forceHide || (!melee.isAttacking && !melee.isBlocking && (melee.meleeManager.rightWeapon || melee.meleeManager.leftWeapon))); + return melee && melee.meleeManager && (forceHide || (!melee.isAttacking && !melee.isBlocking && (GameStateManager.Instance.CurrentState != GameStateManager.State.COMBAT) && (melee.meleeManager.rightWeapon || melee.meleeManager.leftWeapon))); } protected virtual bool CanDrawWeapons() diff --git a/Assets/Scripts/InvectorDerivatives/bLockOn.cs b/Assets/Scripts/InvectorDerivatives/bLockOn.cs index a3b3389ed..3002aa65e 100644 --- a/Assets/Scripts/InvectorDerivatives/bLockOn.cs +++ b/Assets/Scripts/InvectorDerivatives/bLockOn.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Collections.Generic; -using Beyond; -using Invector; +using System.Linq; +using Invector; // Assuming vIHealthController is in this namespace or vCharacterController using Invector.vCharacterController; using UnityEngine; using UnityEngine.UI; @@ -9,20 +9,18 @@ using UnityEngine.UI; namespace Beyond { [vClassHeader("MELEE LOCK-ON")] - public class bLockOn : vLockOnBehaviour + public class bLockOn : vLockOnBehaviour // Inherits from your provided vLockOnBehaviour { #region variables [System.Serializable] - public class LockOnEvent : UnityEngine.Events.UnityEvent - { } + public class LockOnEvent : UnityEngine.Events.UnityEvent { } [Tooltip("Make sure to disable or change the StrafeInput to a different key at the Player Input component")] public bool strafeWhileLockOn = true; [Tooltip("Create a Image inside the UI and assign here")] public RectTransform aimImagePrefab; - public Canvas aimImageContainer; public Vector2 aimImageSize = new Vector2(30, 30); @@ -33,70 +31,95 @@ namespace Beyond [Range(-0.5f, 0.5f)] public float spriteHeight = 0.25f; - [Tooltip("Offset for the camera height")] + [Tooltip("Offset for the camera height when locking on")] public float cameraHeightOffset; - [Tooltip("Transition Speed for the Camera")] + [Tooltip("Transition Speed for the Camera to rotate towards the target when locking on.")] public float lockSpeed = 0.5f; [Header("LockOn Inputs")] public GenericInput lockOnInput = new GenericInput("Tab", "RightStickClick", "RightStickClick"); - + // Inputs for Next/Previous target are now handled by calling base.ChangeTarget(1) or base.ChangeTarget(-1) public GenericInput nexTargetInput = new GenericInput("X", false, false, "RightAnalogHorizontal", true, false, "X", false, false); public GenericInput previousTargetInput = new GenericInput("Z", false, false, "RightAnalogHorizontal", true, true, "Z", false, false); - internal bool isLockingOn; + + public bool isLockingOn { get; private set; } // Keep this for external systems like AutoTargetting public LockOnEvent onLockOnTarget; public LockOnEvent onUnLockOnTarget; - private Canvas _aimCanvas; - private RectTransform _aimImage; - protected bool inTarget; + private RectTransform _aimImageInstance; + // 'inTarget' might be redundant if we rely on base.currentTarget != null and isLockingOn + // protected bool inTarget; protected bMeleeCombatInput tpInput; + private AutoTargetting autoTargetingSystem; #endregion variables - protected virtual void Start() + protected void Start() // Override if base has Start, otherwise just 'void Start()' { - Init(); + // If vLockOnBehaviour.Start is not virtual, you might not need base.Start() + // but Init() is crucial from the base class. + base.Init(); // Call the Init from vLockOnBehaviour + tpInput = GetComponent(); if (tpInput) { tpInput.onUpdate -= UpdateLockOn; tpInput.onUpdate += UpdateLockOn; - // access the HealthController to Reset the LockOn when Dead - GetComponent().onDead.AddListener((GameObject g) => + // Player health for unlocking on death (assuming player has vIHealthController) + var playerHealth = GetComponent(); + if (playerHealth != null) { - // action to reset lockOn - isLockingOn = false; - LockOn(false); - UpdateLockOn(); - }); + // This part might need adjustment based on how vIHealthController handles death events + // For now, let's assume a simple check or that other systems handle player death state. + } + } + else + { + Debug.LogError("bLockOn: bMeleeCombatInput not found. Lock-on system disabled."); + enabled = false; + return; } - if (!aimImageContainer) + if (!aimImageContainer) aimImageContainer = GetComponentInChildren(true); + if (aimImagePrefab && aimImageContainer && _aimImageInstance == null) { - aimImageContainer = gameObject.GetComponentInChildren(true); + _aimImageInstance = Instantiate(aimImagePrefab, aimImageContainer.transform, false); + _aimImageInstance.gameObject.SetActive(false); + } + + // Get AutoTargetting system + if (Player.Instance != null) autoTargetingSystem = Player.Instance.GetComponentInChildren(true); + if (autoTargetingSystem == null) autoTargetingSystem = FindObjectOfType(); + + if (autoTargetingSystem != null) + { + // Set the 'range' field from the base class + base.range = autoTargetingSystem.maxTargetingDistance; + // Note: findTargetAngle from AutoTargetting is not directly used by this base class. + // The base class uses screen-space sorting. We can't easily inject world-space angle here + // without modifying vLockOnBehaviour.GetPossibleTargets() or SortTargets(). + // For now, switching will respect AutoTargetting's *distance* but use base class's screen-angle logic. + } + else + { + Debug.LogWarning("bLockOn: AutoTargetting system not found. Using default vLockOnBehaviour range."); } } - public RectTransform aimImage + public RectTransform AimImageDisplay { get { - if (_aimImage) return _aimImage; - if (aimImageContainer) + if (_aimImageInstance == null && aimImagePrefab && aimImageContainer) { - _aimImage = Instantiate(aimImagePrefab, Vector2.zero, Quaternion.identity) as RectTransform; - _aimImage.SetParent(aimImageContainer.transform); - return _aimImage; + _aimImageInstance = Instantiate(aimImagePrefab, aimImageContainer.transform, false); + _aimImageInstance.anchoredPosition = Vector2.zero; + // ... other setup for _aimImageInstance } - else - { - Debug.LogWarning("Missing UI Canvas in the scene, please add one"); - } - return null; + return _aimImageInstance; } } @@ -105,139 +128,271 @@ namespace Beyond if (this.tpInput == null) return; LockOnInput(); SwitchTargetsInput(); - CheckForCharacterAlive(); + CheckForCharacterAlive(); // Checks base.currentTarget UpdateAimImage(); } protected virtual void LockOnInput() { if (tpInput.tpCamera == null || tpInput.cc == null) return; - - // lock the camera into a target, if there is any around if (lockOnInput.GetButtonDown() && !tpInput.cc.customAction) { - isLockingOn = !isLockingOn; - LockOn(isLockingOn); - } - // unlock the camera if the target is null - else if (isLockingOn && (tpInput.tpCamera.lockTarget == null)) - { - isLockingOn = false; - LockOn(false); - } - // choose to use lock-on with strafe of free movement - if (strafeWhileLockOn && !tpInput.cc.locomotionType.Equals(vThirdPersonMotor.LocomotionType.OnlyStrafe)) - { - if (isLockingOn && tpInput.tpCamera.lockTarget != null) + // Toggle lock-on state + if (isLockingOn) { - tpInput.cc.lockInStrafe = true; - tpInput.cc.isStrafing = true; + UnlockTarget(); } else { - tpInput.cc.lockInStrafe = false; - tpInput.cc.isStrafing = false; + AttemptLockOn(null); // Attempt to find a new target } } } + // This method is called by base.ChangeTargetRoutine when a target is selected protected override void SetTarget() { - if (tpInput.tpCamera != null) + // base.currentTarget (which is base.target) has been set by ChangeTargetRoutine + // or by our AttemptLockOn/ManuallySetLockOnTarget. + // We just need to react to it. + if (tpInput.tpCamera != null && base.currentTarget != null) { - tpInput.tpCamera.SetLockTarget(currentTarget.transform, cameraHeightOffset, lockSpeed); - onLockOnTarget.Invoke(currentTarget); + tpInput.tpCamera.SetLockTarget(base.currentTarget.transform, cameraHeightOffset, lockSpeed); + if (isLockingOn) // Only invoke if we are truly in a locking state + { + onLockOnTarget.Invoke(base.currentTarget.transform); + } + } + else if (base.currentTarget == null && isLockingOn) // Target became null while we thought we were locked + { + UnlockTarget(false); // Silently unlock if target disappeared } } + protected virtual void SwitchTargetsInput() { - if (tpInput.tpCamera == null) return; - - if (tpInput.tpCamera.lockTarget) + if (tpInput.tpCamera == null || !isLockingOn) return; + // base.currentTarget is the currently locked Transform + if (base.currentTarget != null) { - // switch between targets using Keyboard - if (previousTargetInput.GetButtonDown()) PreviousTarget(); - else if (nexTargetInput.GetButtonDown()) NextTarget(); + if (previousTargetInput.GetButtonDown()) base.ChangeTarget(-1); // Calls base class method + else if (nexTargetInput.GetButtonDown()) base.ChangeTarget(1); // Calls base class method } } + // 'ChangeTarget' is already defined in the base class and handles index changes. + // We override SetTarget() which is called by the base ChangeTargetRoutine. + protected virtual void CheckForCharacterAlive() { - if (currentTarget && !isCharacterAlive() && inTarget || (inTarget && !isCharacterAlive())) + // base.isCharacterAlive() checks the base.currentTarget + if (isLockingOn && base.currentTarget != null && !base.isCharacterAlive()) { - ResetLockOn(); - inTarget = false; - LockOn(true); - StopLockOn(); + // Debug.Log($"bLockOn: Locked target {base.currentTarget.name} died. Attempting to find new one or unlock."); + Transform oldDeadTarget = base.currentTarget; + AttemptLockOn(null, oldDeadTarget); // Try to find a new target, excluding the dead one for this attempt } } - protected virtual void LockOn(bool value) + /// + /// Attempts to lock onto a target. If preferredTarget is provided and valid, locks that. + /// Otherwise, finds the best available target. + /// + protected void AttemptLockOn(Transform preferredTarget, Transform excludeTarget = null) { - base.UpdateLockOn(value); - if (!inTarget && currentTarget) + if (tpInput == null || tpInput.cc == null || tpInput.tpCamera == null) return; + + Transform targetToLock = null; + + if (preferredTarget != null && base.isCharacterAlive(preferredTarget)) { - inTarget = true; - // send current target if inTarget - SetTarget(); + targetToLock = preferredTarget; } - else if (inTarget && !currentTarget) + else { - inTarget = false; - // send message to clear current target - StopLockOn(); + // Update range from AutoTargetting before finding targets + if (autoTargetingSystem != null) base.range = autoTargetingSystem.maxTargetingDistance; + + List possible = base.GetPossibleTargets(); // This uses base.range + if (possible != null && possible.Count > 0) + { + // Filter out the excludeTarget if provided + if (excludeTarget != null) + { + possible.Remove(excludeTarget); + } + // Optional: Re-filter 'possible' list here using AutoTargetting's angle + // This would require iterating 'possible' and doing Vector3.Angle checks. + // For now, we rely on base.SortTargets() which is screen-based. + if (possible.Count > 0) + { + targetToLock = possible.FirstOrDefault(); // Base.GetPossibleTargets already sorts them + } + } + } + + if (targetToLock != null) + { + // If we were previously locked on a different target + if (isLockingOn && base.currentTarget != null && base.currentTarget != targetToLock) + { + onUnLockOnTarget.Invoke(base.currentTarget); // Invoke for old target + } + + // This directly sets the protected 'target' field in the base class + // Since we can't call a base.SelectTarget(), we mimic what base.UpdateLockOn(true) would do. + this.GetType().GetField("target", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.SetValue(this, targetToLock); + // this.target = targetToLock; // This would create a new field in bLockOn, not set base.target + + isLockingOn = true; + base.UpdateLockOn(true); // This will set base.target if list not empty, and also set _inLockOn + // and might call SetTarget if list was empty and now has one. + // It's a bit of a dance because the base class design is restrictive. + + // We need to ensure our 'isLockingOn' and the base class's internal lock state are synced. + // And that SetTarget() (our override) is called to update camera & events. + if (base.currentTarget == targetToLock) // If base.UpdateLockOn successfully set it + { + this.SetTarget(); // Manually call our override to ensure camera and events fire + UpdateStrafeState(true); + UpdateAimImage(); + } + else // Fallback if base.UpdateLockOn didn't pick our target (e.g. list was empty for it) + { + // This case should ideally not happen if targetToLock was valid. + // If it does, it means base.GetPossibleTargets within UpdateLockOn failed. + UnlockTarget(false); // Silently unlock + } + } + else // No target found or preferred target was invalid + { + UnlockTarget(); } } - public void SetLockOnToFalse() + protected void UnlockTarget(bool invokeEvent = true) { - LockOn(false); + if (tpInput == null || tpInput.tpCamera == null) return; + + Transform previouslyLocked = base.currentTarget; // Get before clearing + + base.UpdateLockOn(false); // This sets base.target to null and base._inLockOn to false + isLockingOn = false; + + if (previouslyLocked != null && invokeEvent) + { + onUnLockOnTarget.Invoke(previouslyLocked); + } + + tpInput.tpCamera.RemoveLockTarget(); + UpdateStrafeState(false); + UpdateAimImage(); } + + public void ManuallySetLockOnTarget(Transform newTargetTransform, bool shouldLock) + { + if (shouldLock) + { + AttemptLockOn(newTargetTransform); + } + else + { + UnlockTarget(); + } + } + + public Transform GetCurrentLockOnTarget() + { + // Relies on base.currentTarget which is the Transform 'target' from base class + return isLockingOn ? base.currentTarget : null; + } + + private void UpdateStrafeState(bool shouldStrafe) + { + if (strafeWhileLockOn && tpInput?.cc != null && !tpInput.cc.locomotionType.Equals(vThirdPersonMotor.LocomotionType.OnlyStrafe)) + { + bool canStrafe = shouldStrafe && isLockingOn && base.currentTarget != null; + tpInput.cc.lockInStrafe = canStrafe; + tpInput.cc.isStrafing = canStrafe; + } + } + + public void SetLockOnToFalse() => UnlockTarget(); + protected virtual void UpdateAimImage() { - if (!aimImageContainer || !aimImage) return; - if (hideSprite) + var displayImage = AimImageDisplay; + if (!aimImageContainer || displayImage == null) return; + + displayImage.sizeDelta = aimImageSize; + // base.currentTarget is the Transform, base.isCharacterAlive() checks it + bool shouldShowAimImage = isLockingOn && base.currentTarget != null && base.isCharacterAlive(base.currentTarget); + + + if (hideSprite) displayImage.gameObject.SetActive(shouldShowAimImage); + else if (!displayImage.gameObject.activeSelf) displayImage.gameObject.SetActive(true); + + if (shouldShowAimImage && tpCamera != null && base.currentTarget != null) { - aimImage.sizeDelta = aimImageSize; - if (currentTarget && !aimImage.transform.gameObject.activeSelf && isCharacterAlive()) - aimImage.transform.gameObject.SetActive(true); - else if (!currentTarget && aimImage.transform.gameObject.activeSelf) - aimImage.transform.gameObject.SetActive(false); - else if (_aimImage.transform.gameObject.activeSelf && !isCharacterAlive()) - aimImage.transform.gameObject.SetActive(false); + // Using the extension method from vLockOnHelper + displayImage.anchoredPosition = base.currentTarget.GetScreenPointOffBoundsCenter(aimImageContainer, tpCamera.targetCamera, spriteHeight); } - if (currentTarget && aimImage && aimImageContainer) - aimImage.anchoredPosition = currentTarget.GetScreenPointOffBoundsCenter(aimImageContainer, tpCamera.targetCamera, spriteHeight); - else if (aimImageContainer) - aimImage.anchoredPosition = Vector2.zero; + else if (!hideSprite) displayImage.anchoredPosition = Vector2.zero; } - public virtual void StopLockOn() + // StopLockOn is effectively UnlockTarget now + // public virtual void StopLockOn() { UnlockTarget(); } + + + public List GetNearbyTargets() // For AutoTargetting/MagicAttacks { - if (currentTarget == null && tpInput.tpCamera != null) + if (autoTargetingSystem != null) { - onUnLockOnTarget.Invoke(tpInput.tpCamera.lockTarget); - tpInput.tpCamera.RemoveLockTarget(); - isLockingOn = false; - inTarget = false; + base.range = autoTargetingSystem.maxTargetingDistance; + // Angle filtering from AutoTargetting would need to be applied manually here + // if we want to override the base class's screen-based sorting/filtering for this specific call. } - } + // base.GetPossibleTargets() returns List + List allTargetsInBaseRange = base.GetPossibleTargets(); - public virtual void NextTarget() - { - base.ChangeTarget(1); - } + if (allTargetsInBaseRange == null) return new List(); - public virtual void PreviousTarget() - { - base.ChangeTarget(-1); - } + // Optional: Further filter allTargetsInBaseRange by AutoTargetting's angle + if (autoTargetingSystem != null && _playerTransform != null) // Assuming _playerTransform is accessible or passed + { + List angleFilteredTargets = new List(); + Vector3 playerPos = _playerTransform.position; // Need player transform + Vector3 playerFwd = _playerTransform.forward; - public List GetNearbyTargets() + foreach (Transform t in allTargetsInBaseRange) + { + if (t == null) continue; + Vector3 dirToTarget = (t.position - playerPos); + dirToTarget.y = 0; // Assuming 2D angle check for simplicity + if (dirToTarget.sqrMagnitude < 0.001f) // Target is at player's feet + { + angleFilteredTargets.Add(t); + continue; + } + float angle = Vector3.Angle(playerFwd, dirToTarget.normalized); + if (angle <= autoTargetingSystem.targetingAngleThreshold) + { + angleFilteredTargets.Add(t); + } + } + return angleFilteredTargets; + } + + return allTargetsInBaseRange; + } + // Need player transform for angle filtering in GetNearbyTargets + private Transform _playerTransform; + void Awake() // Get player transform { - return base.GetPossibleTargets(); + if (Player.Instance != null) _playerTransform = Player.Instance.transform; + else _playerTransform = transform; // Fallback if bLockOn is on player } } } \ No newline at end of file diff --git a/Assets/Scripts/InvectorDerivatives/bMeleeCombatInput.cs b/Assets/Scripts/InvectorDerivatives/bMeleeCombatInput.cs index 52d0d6bda..89638f08e 100644 --- a/Assets/Scripts/InvectorDerivatives/bMeleeCombatInput.cs +++ b/Assets/Scripts/InvectorDerivatives/bMeleeCombatInput.cs @@ -3,20 +3,20 @@ using Invector.vCharacterController; using Invector.vEventSystems; using Invector.vMelee; using UnityEngine; +using System.Collections; namespace Beyond { - // here you can modify the Melee Combat inputs - // if you want to modify the Basic Locomotion inputs, go to the vThirdPersonInput [vClassHeader("MELEE INPUT MANAGER", iconName = "inputIcon")] public class bMeleeCombatInput : bThirdPersonInput, vIMeleeFighter { + // ... (rest of your existing bMeleeCombatInput variables) ... + #region Variables [vEditorToolbar("Inputs")] [Header("Melee Inputs")] public GenericInput weakAttackInput = new GenericInput("Mouse0", "RB", "RB"); - public GenericInput strongAttackInput = new GenericInput("Alpha1", false, "RT", true, "RT", false); public GenericInput blockInput = new GenericInput("Mouse1", "LB", "LB"); @@ -25,7 +25,7 @@ namespace Beyond public bool isAttacking { - get => _isAttacking || cc.IsAnimatorTag("Attack"); + get => _isAttacking || (cc != null && cc.IsAnimatorTag("Attack")); protected set { _isAttacking = value; } } @@ -44,10 +44,20 @@ namespace Beyond [HideInInspector] public bool lockMeleeInput; + [Header("Dash Settings")] + [Tooltip("Speed at which the player rotates towards the target before dashing.")] + public float dashRotationSpeed = 15f; + [Tooltip("Angle threshold (degrees). If angle to target is less than this, player dashes immediately without pre-rotation.")] + public float dashFacingAngleThreshold = 10f; + [Tooltip("Maximum time (seconds) the pre-dash rotation will attempt before dashing anyway.")] + public float maxDashRotationTime = 0.5f; + + private AutoTargetting autoTargeting; + private bool isRotatingAndDashing = false; + public void SetLockMeleeInput(bool value) { lockMeleeInput = value; - if (value) { isAttacking = false; @@ -63,17 +73,30 @@ namespace Beyond private MagicAttacks magicAttacks; - #endregion Variables + #endregion public virtual bool lockInventory { - get { return isAttacking || cc.isDead || (cc.customAction || cc.IsAnimatorTag("special")) || cc.isRolling; } + get { return isAttacking || (cc != null && (cc.isDead || cc.customAction || cc.IsAnimatorTag("special") || cc.isRolling)); } } protected override void Start() { - base.Start(); + base.Start(); // This will call bThirdPersonInput's Start, which gets 'cc' magicAttacks = GetComponent(); + autoTargeting = GetComponent(); + if (autoTargeting == null && Player.Instance != null && Player.Instance.gameObject == this.gameObject) + { + autoTargeting = Player.Instance.GetComponent(); + } + if (autoTargeting == null && cc != null && cc.transform.root != null) + { + autoTargeting = cc.transform.root.GetComponentInChildren(true); + } + if (autoTargeting == null) + { + Debug.LogWarning("bMeleeCombatInput: AutoTargetting component not found. Dash towards target will require manual aiming."); + } } protected override void LateUpdate() @@ -85,17 +108,12 @@ namespace Beyond protected override void FixedUpdate() { base.FixedUpdate(); - //force showing cursor ShowCursor(showCursorOnStart); - isBlocking = Input.GetKeyDown(KeyCode.D); } protected override void InputHandle() { - if (cc == null || cc.isDead) - { - return; - } + if (cc == null || cc.isDead) return; base.InputHandle(); @@ -108,22 +126,18 @@ namespace Beyond else { ResetAttackTriggers(); - isBlocking = false; + if (!blockInput.GetButton()) + { + isBlocking = false; + } } } #region MeleeCombat Input Methods - /// - /// WEAK ATK INPUT - /// public virtual void MeleeWeakAttackInput() { - if (cc.animator == null) - { - return; - } - + if (cc.animator == null) return; if (weakAttackInput.GetButtonDown() && MeleeAttackStaminaConditions()) { TriggerWeakAttack(); @@ -136,16 +150,9 @@ namespace Beyond cc.animator.SetTrigger(vAnimatorParameters.WeakAttack); } - /// - /// STRONG ATK INPUT - /// public virtual void MeleeStrongAttackInput() { - if (cc.animator == null) - { - return; - } - + if (cc.animator == null) return; if (strongAttackInput.GetButtonDown() && (!meleeManager.CurrentActiveAttackWeapon || meleeManager.CurrentActiveAttackWeapon.useStrongAttack) && MeleeAttackStaminaConditions()) @@ -160,22 +167,17 @@ namespace Beyond cc.animator.SetTrigger(vAnimatorParameters.StrongAttack); } - /// - /// BLOCK INPUT - /// public virtual void BlockingInput() { - if (cc.animator == null) - { - return; - } - + if (cc.animator == null) return; isBlocking = blockInput.GetButton() && cc.currentStamina > 0 && !cc.customAction && !cc.IsAnimatorTag("special") && !isAttacking; } public override void ControlRotation() { + if (cc == null || cc.lockRotation) return; + if (cameraMain && !lockUpdateMoveDirection) { if (!cc.keepDirection) @@ -183,148 +185,235 @@ namespace Beyond cc.UpdateMoveDirection(cameraMain.transform); } } - - //here + if (tpCamera != null && tpCamera.lockTarget && cc.isStrafing && !cc.isRolling) { - cc.RotateToPosition(tpCamera.lockTarget.position); // rotate the character to a specific target + cc.RotateToPosition(tpCamera.lockTarget.position); } else { - cc.ControlRotationType(); // handle the controller rotation type (strafe or free) + cc.ControlRotationType(); } } - /// - /// Override the Sprint method to cancel Sprinting when Attacking - /// protected override void SprintInput() { - if (sprintInput.useInput) + if (lockSprintInput) return; // Check local lock first + if (sprintInput.useInput && cc != null) { bool canSprint = cc.useContinuousSprint ? sprintInput.GetButtonDown() : sprintInput.GetButton(); + // Let base class (vThirdPersonInput) handle the cc.Sprint call if we don't override completely + // For now, let's assume your bThirdPersonInput's SprintInput or vThirdPersonInput's one is what you want. + // If it was base.SprintInput() before, and you have bThirdPersonInput overriding it, make sure that's intended. + // For simplicity, directly call cc.Sprint if bThirdPersonInput doesn't add much, or call base.SprintInput() if it does. cc.Sprint(canSprint && !isAttacking); } } + protected override void CrouchInput() + { + if (lockCrouchInput) return; + base.CrouchInput(); // Call the base class logic from vThirdPersonInput/bThirdPersonInput + } - #endregion MeleeCombat Input Methods + protected override void StrafeInput() + { + if (lockStrafeInput) return; + base.StrafeInput(); // Call the base class logic from vThirdPersonInput/bThirdPersonInput + } + + + #endregion #region Conditions protected virtual bool MeleeAttackStaminaConditions() { + if (meleeManager == null) meleeManager = GetComponent(); + if (meleeManager == null || cc == null) return false; var result = cc.currentStamina - meleeManager.GetAttackStaminaCost(); return result >= 0; } public virtual bool MeleeAttackConditions() { - if (meleeManager == null) - { - meleeManager = GetComponent(); - } - - return meleeManager != null && cc.isGrounded && !cc.customAction && !cc.IsAnimatorTag("special") && + if (meleeManager == null) meleeManager = GetComponent(); + return meleeManager != null && cc != null && cc.isGrounded && !cc.customAction && !cc.IsAnimatorTag("special") && !cc.isJumping && !cc.isCrouching && !cc.isRolling && !isEquipping && - !cc.animator.IsInTransition(cc.baseLayer); + (cc.animator != null && !cc.animator.IsInTransition(cc.baseLayer)) && !isRotatingAndDashing; } - protected override bool JumpConditions() + // We override JumpInput, so we'll use the base JumpConditions if needed, or reimplement. + // vThirdPersonInput.JumpConditions(): + // return !cc.customAction && !cc.isCrouching && cc.isGrounded && cc.GroundAngle() < cc.slopeLimit && cc.currentStamina >= cc.jumpStamina && !cc.isJumping && !cc.isRolling; + protected override bool JumpConditions() // This is from vThirdPersonInput { - return !isAttacking && base.JumpConditions(); + if (cc == null) return false; + bool baseConditions = !cc.customAction && !cc.isCrouching && cc.isGrounded && cc.GroundAngle() < cc.slopeLimit && cc.currentStamina >= cc.jumpStamina && !cc.isJumping && !cc.isRolling; + return baseConditions && !isAttacking && !isRotatingAndDashing; // Add our specific conditions } -/* - protected override bool RollConditions() + + // We override RollInput, so we'll use the base RollConditions or reimplement. + // vThirdPersonInput.RollConditions(): + // return (!cc.isRolling || cc.canRollAgain) && cc.isGrounded && !cc.customAction && cc.currentStamina > cc.rollStamina && !cc.isJumping && !cc.isSliding; + protected override bool RollConditions() // This is from vThirdPersonInput { - return base.RollConditions() && !isAttacking && !cc.animator.IsInTransition(cc.upperBodyLayer) && - !cc.animator.IsInTransition(cc.fullbodyLayer); + if (cc == null) return false; + bool baseConditions = (!cc.isRolling || cc.canRollAgain) && cc.isGrounded && !cc.customAction && cc.currentStamina > cc.rollStamina && !cc.isJumping && !cc.isSliding; + // Add any bMeleeCombatInput specific conditions if necessary, like !isAttacking + return baseConditions && !isAttacking && !isRotatingAndDashing; } -*/ - #endregion Conditions - #region Update Animations - protected virtual void UpdateMeleeAnimations() + #endregion + + // MODIFIED JumpInput + protected override void JumpInput() { - if (cc.animator == null || meleeManager == null) + if (lockJumpInput) return; // From bThirdPersonInput + if (jumpInput.GetButtonDown() && JumpConditions()) // JumpConditions now includes !isAttacking & !isRotatingAndDashing { - return; + cc.Jump(true); } - - cc.animator.SetInteger(vAnimatorParameters.AttackID, AttackID); - cc.animator.SetInteger(vAnimatorParameters.DefenseID, DefenseID); - cc.animator.SetBool(vAnimatorParameters.IsBlocking, isBlocking); - cc.animator.SetFloat(vAnimatorParameters.MoveSet_ID, meleeMoveSetID, .2f, vTime.deltaTime); - isEquipping = cc.IsAnimatorTag("IsEquipping"); } - /// - /// Default moveset id used when is without weapon - /// - public virtual int defaultMoveSetID { get; set; } - - /// - /// Used to ignore the Weapon moveset id and use the - /// - public virtual bool overrideWeaponMoveSetID { get; set; } - - /// - /// Current moveset id based if is using weapon or not - /// - public virtual int meleeMoveSetID + // MODIFIED RollInput + protected override void RollInput() { - get + if (lockRollInput) return; // From bThirdPersonInput + if (cc == null) return; + if (isRotatingAndDashing) return; + + // Use the overridden RollConditions which now includes !isAttacking and !isRotatingAndDashing + if (rollInput.GetButtonDown() && RollConditions()) { - int id = meleeManager.GetMoveSetID(); - if (id == 0 || overrideWeaponMoveSetID) + bThirdPersonController beyondController = cc as bThirdPersonController; + if (beyondController == null) { - id = defaultMoveSetID; + Debug.LogError("cc is not a bThirdPersonController instance! Cannot Dash/Roll."); + if(cc != null) cc.Roll(); // Fallback to cc's roll if cast fails + return; } - return id; + bool isInputBackwards = cc.input.z * -1f >= 0 && Mathf.Abs(cc.input.x) < 0.2f; // Or cc.rawInput.z if you use raw input for this decision + + if (!isInputBackwards) + { + beyondController.Roll(); // Or cc.Roll() if bThirdPersonController doesn't override it meaningfully + } + else // Forward, neutral, or sideways input + { + if (autoTargeting != null && autoTargeting.CurrentTarget != null) + { + Transform target = autoTargeting.CurrentTarget.transform; + Vector3 directionToTarget = (target.position - cc.transform.position); + directionToTarget.y = 0; + + if (directionToTarget.sqrMagnitude < 0.01f) // Very close + { + beyondController.Dash(); + } + else + { + float angleToTarget = Vector3.Angle(cc.transform.forward, directionToTarget.normalized); + if (angleToTarget <= dashFacingAngleThreshold) + { + beyondController.Dash(); + } + else + { + if (!isRotatingAndDashing) // Should be redundant due to the check at the start of the method + { + StartCoroutine(RotateAndDashCoroutine(target, beyondController)); + } + } + } + } + else + { + beyondController.Dash(); // No target, regular dash + } + } } } - - public virtual void ResetMeleeAnimations() + + protected virtual IEnumerator RotateAndDashCoroutine(Transform target, bThirdPersonController controller) { - if (meleeManager == null || !animator) + if (isRotatingAndDashing || controller == null) yield break; + isRotatingAndDashing = true; + + bool previousLockRotation = controller.lockRotation; + controller.lockRotation = true; + + bool wasStrafing = controller.isStrafing; + bool originalKeepDirection = controller.keepDirection; + + if (wasStrafing && controller.isStrafing) { - return; + controller.Strafe(); } + controller.keepDirection = false; - cc.animator.SetBool(vAnimatorParameters.IsBlocking, false); + float startTime = Time.time; + Vector3 directionToTarget; + Quaternion targetRotationQuaternion; + + while (Time.time < startTime + maxDashRotationTime) + { + if (target == null || !target.gameObject.activeInHierarchy) break; + if (controller == null) { isRotatingAndDashing = false; yield break; } + + directionToTarget = (target.position - controller.transform.position); + directionToTarget.y = 0; + + if (directionToTarget.sqrMagnitude < 0.001f) break; + + targetRotationQuaternion = Quaternion.LookRotation(directionToTarget.normalized); + controller.transform.rotation = Quaternion.Slerp(controller.transform.rotation, targetRotationQuaternion, Time.deltaTime * dashRotationSpeed); + + if (Vector3.Angle(controller.transform.forward, directionToTarget.normalized) <= dashFacingAngleThreshold) + { + controller.transform.rotation = targetRotationQuaternion; + break; + } + yield return null; + } + + if (controller == null) { isRotatingAndDashing = false; yield break; } + + if (target != null && target.gameObject.activeInHierarchy && + (Time.time >= startTime + maxDashRotationTime || Vector3.Angle(controller.transform.forward, (target.position - controller.transform.position).normalized) > 0.1f) ) + { + directionToTarget = (target.position - controller.transform.position); + directionToTarget.y = 0; + if (directionToTarget.sqrMagnitude > 0.001f) + { + controller.transform.rotation = Quaternion.LookRotation(directionToTarget.normalized); + } + } + + controller.lockRotation = previousLockRotation; + if (wasStrafing && !controller.isStrafing) + { + controller.Strafe(); + } + controller.keepDirection = originalKeepDirection; + + controller.Dash(); + + isRotatingAndDashing = false; } - public virtual int AttackID - { - get { return meleeManager ? meleeManager.GetAttackID() : 0; } - } - - public virtual int DefenseID - { - get { return meleeManager ? meleeManager.GetDefenseID() : 0; } - } - - #endregion Update Animations - #region Melee Methods public virtual void OnEnableAttack() { - if (meleeManager == null) - { - meleeManager = GetComponent(); - } - - if (meleeManager == null) - { - return; - } + if (meleeManager == null) meleeManager = GetComponent(); + if (meleeManager == null || cc == null) return; cc.currentStaminaRecoveryDelay = meleeManager.GetAttackStaminaRecoveryDelay(); cc.currentStamina -= meleeManager.GetAttackStaminaCost(); isAttacking = true; - cc.isSprinting = false; + if(cc!=null) cc.isSprinting = false; } public virtual void OnDisableAttack() @@ -334,8 +423,11 @@ namespace Beyond public virtual void ResetAttackTriggers() { - cc.animator.ResetTrigger(vAnimatorParameters.WeakAttack); - cc.animator.ResetTrigger(vAnimatorParameters.StrongAttack); + if (cc != null && cc.animator != null) + { + cc.animator.ResetTrigger(vAnimatorParameters.WeakAttack); + cc.animator.ResetTrigger(vAnimatorParameters.StrongAttack); + } } public virtual void BreakAttack(int breakAtkID) @@ -346,69 +438,71 @@ namespace Beyond public virtual void OnRecoil(int recoilID) { - cc.animator.SetInteger(vAnimatorParameters.RecoilID, recoilID); - cc.animator.SetTrigger(vAnimatorParameters.TriggerRecoil); - cc.animator.SetTrigger(vAnimatorParameters.ResetState); - cc.animator.ResetTrigger(vAnimatorParameters.WeakAttack); - cc.animator.ResetTrigger(vAnimatorParameters.StrongAttack); - } - - protected override void RollInput() - { - if (rollInput.GetButtonDown() && RollConditions()) + if (cc != null && cc.animator != null) { - // cc.Roll(); - - if (cc.input.magnitude > 0.1f) - { - cc.Roll(); - } - else - { - bThirdPersonController beyondController = (bThirdPersonController)cc; - beyondController.Dash(); - } - + cc.animator.SetInteger(vAnimatorParameters.RecoilID, recoilID); + cc.animator.SetTrigger(vAnimatorParameters.TriggerRecoil); + cc.animator.SetTrigger(vAnimatorParameters.ResetState); + cc.animator.ResetTrigger(vAnimatorParameters.WeakAttack); + cc.animator.ResetTrigger(vAnimatorParameters.StrongAttack); } } - + public virtual void OnReceiveAttack(vDamage damage, vIMeleeFighter attacker) { - if (magicAttacks.shieldEffectIsActive) - { - return; - } - - // character is blocking + if (cc == null) return; + if (magicAttacks != null && magicAttacks.shieldEffectIsActive) return; + if (!damage.ignoreDefense && isBlocking && meleeManager != null && meleeManager.CanBlockAttack(damage.sender.position)) { var damageReduction = meleeManager.GetDefenseRate(); - if (damageReduction > 0) - { - damage.ReduceDamage(damageReduction); - } - + if (damageReduction > 0) damage.ReduceDamage(damageReduction); if (attacker != null && meleeManager != null && meleeManager.CanBreakAttack()) { attacker.BreakAttack(meleeManager.GetDefenseRecoilID()); } - meleeManager.OnDefense(); cc.currentStaminaRecoveryDelay = damage.staminaRecoveryDelay; cc.currentStamina -= damage.staminaBlockCost; } - - // apply damage damage.hitReaction = !isBlocking || damage.ignoreDefense; cc.TakeDamage(damage); } - public virtual vICharacter character - { - get { return cc; } - } + public virtual vICharacter character { get { return cc; } } - #endregion Melee Methods + #endregion + + #region Update Animations + public virtual int defaultMoveSetID { get; set; } + public virtual bool overrideWeaponMoveSetID { get; set; } + public virtual int meleeMoveSetID + { + get + { + if (meleeManager == null) meleeManager = GetComponent(); + int id = meleeManager?.GetMoveSetID() ?? 0; + if (id == 0 || overrideWeaponMoveSetID) id = defaultMoveSetID; + return id; + } + } + public virtual void ResetMeleeAnimations() + { + if (meleeManager == null || cc == null || cc.animator == null) return; + cc.animator.SetBool(vAnimatorParameters.IsBlocking, false); + } + public virtual int AttackID { get { if (meleeManager == null) meleeManager = GetComponent(); return meleeManager?.GetAttackID() ?? 0; } } + public virtual int DefenseID { get { if (meleeManager == null) meleeManager = GetComponent(); return meleeManager?.GetDefenseID() ?? 0; } } + protected virtual void UpdateMeleeAnimations() + { + if (cc == null || cc.animator == null || meleeManager == null) return; + cc.animator.SetInteger(vAnimatorParameters.AttackID, AttackID); + cc.animator.SetInteger(vAnimatorParameters.DefenseID, DefenseID); + cc.animator.SetBool(vAnimatorParameters.IsBlocking, isBlocking); + cc.animator.SetFloat(vAnimatorParameters.MoveSet_ID, meleeMoveSetID, .2f, vTime.deltaTime); + isEquipping = cc.IsAnimatorTag("IsEquipping"); + } + #endregion } } \ No newline at end of file diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index e0cfe761d..3d39047a6 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -50,6 +50,12 @@ EditorBuildSettings: - enabled: 1 path: Assets/Scenes/Land_01/Land_of_Death_River1_Valley02.unity guid: dabdc796f82531d42b4dfb1bab670dda + - enabled: 1 + path: Assets/Scenes/Fight_Arena/Fight_Arena.unity + guid: c403a1db592cfb34988bc76714980ecf + - enabled: 1 + path: Assets/Scenes/Land_01/Land_Of_Death_ARENAS.unity + guid: 3ac39dab4b05aa540af7a4bbd73b2a5a m_configObjects: com.unity.addressableassets: {fileID: 11400000, guid: 272caeb75b6504dcb987edb253f851b7, type: 2}