diff --git a/Assets/Prefabs/Characters/Beasiculus/Bascileus_BaseModel.prefab b/Assets/Prefabs/Characters/Beasiculus/Bascileus_BaseModel.prefab index 7c131ed28..7c4ff1664 100644 --- a/Assets/Prefabs/Characters/Beasiculus/Bascileus_BaseModel.prefab +++ b/Assets/Prefabs/Characters/Beasiculus/Bascileus_BaseModel.prefab @@ -105638,6 +105638,8 @@ MonoBehaviour: strafeRollBackwardAnim: Roll_Backward strafeRollLeftAnim: Roll_Left strafeRollRightAnim: Roll_Right + strafeRollLeftCorrectionAngle: 45 + strafeRollRightCorrectionAngle: 70 --- !u!114 &9202663235077955828 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/Prefabs/PlayerWithUI.prefab b/Assets/Prefabs/PlayerWithUI.prefab index 91ef9cc24..49b5eb916 100644 --- a/Assets/Prefabs/PlayerWithUI.prefab +++ b/Assets/Prefabs/PlayerWithUI.prefab @@ -18993,6 +18993,7 @@ MonoBehaviour: highlightFadeDuration: 0.3 preferSkinnedMeshRenderer: 1 autoLockSelectedTarget: 1 + alwaysLockOnInCombat: 1 targetLockSystem: {fileID: 0} manualSwitchCooldownDuration: 0.75 --- !u!4 &6425420852750441961 stripped diff --git a/Assets/Scenes/Fight_Arena/Fight_Arena.unity b/Assets/Scenes/Fight_Arena/Fight_Arena.unity index b5bc2bedf..e336adcfb 100644 --- a/Assets/Scenes/Fight_Arena/Fight_Arena.unity +++ b/Assets/Scenes/Fight_Arena/Fight_Arena.unity @@ -16212,6 +16212,36 @@ PrefabInstance: propertyPath: groundLayer.m_Bits value: 268435457 objectReference: {fileID: 0} + - target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5, + type: 3} + propertyPath: strafeRollDuration + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5, + type: 3} + propertyPath: strafeRollLeftAngle + value: -100 + objectReference: {fileID: 0} + - target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5, + type: 3} + propertyPath: strafeRollRightAngle + value: 100 + objectReference: {fileID: 0} + - target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5, + type: 3} + propertyPath: blockFreeModeForwardRoll + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5, + type: 3} + propertyPath: strafeRollCorrectionAngle + value: 71.9 + objectReference: {fileID: 0} + - target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5, + type: 3} + propertyPath: strafeRollLeftCorrectionAngle + value: 70.6 + objectReference: {fileID: 0} - target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5, type: 3} propertyPath: _onReceiveDamage.m_PersistentCalls.m_Calls.Array.size @@ -16297,6 +16327,11 @@ PrefabInstance: propertyPath: m_IsActive value: 1 objectReference: {fileID: 0} + - target: {fileID: 5054440825960491160, guid: 851e8e61247888340bdec90fc8aa37f5, + type: 3} + propertyPath: targetingAngleThreshold + value: 120 + objectReference: {fileID: 0} - target: {fileID: 5083365752064418721, guid: 851e8e61247888340bdec90fc8aa37f5, type: 3} propertyPath: m_AnchorMax.x @@ -17938,6 +17973,10 @@ PrefabInstance: insertIndex: -1 addedObject: {fileID: 1681403708} m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 5935883485110830931, guid: 851e8e61247888340bdec90fc8aa37f5, + type: 3} + insertIndex: -1 + addedObject: {fileID: 1516777741} - targetCorrespondingSourceObject: {fileID: 7577249534690070221, guid: 851e8e61247888340bdec90fc8aa37f5, type: 3} insertIndex: -1 @@ -19764,6 +19803,12 @@ Material: - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} m_BuildTextureStacks: [] m_AllowLocking: 1 +--- !u!1 &1447701206 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 5935883485110830931, guid: 851e8e61247888340bdec90fc8aa37f5, + type: 3} + m_PrefabInstance: {fileID: 1004473948} + m_PrefabAsset: {fileID: 0} --- !u!21 &1461622385 Material: serializedVersion: 8 @@ -20136,7 +20181,7 @@ MonoBehaviour: type: 3} m_PrefabInstance: {fileID: 1004473948} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} + m_GameObject: {fileID: 1447701206} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 38b9f160099a9484b936b7192dd82968, type: 3} @@ -20148,12 +20193,24 @@ MonoBehaviour: type: 3} m_PrefabInstance: {fileID: 1004473948} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} + m_GameObject: {fileID: 1447701206} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 90fb639067d504fc69e8ac3a3fa2283e, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!114 &1516777741 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1447701206} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a069af71c57a846598551f9d5bf36cef, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!21 &1523257492 Material: serializedVersion: 8 diff --git a/Assets/Scripts/Characters/AutoTargetting.cs b/Assets/Scripts/Characters/AutoTargetting.cs index d9983dc56..48fe8c6a0 100644 --- a/Assets/Scripts/Characters/AutoTargetting.cs +++ b/Assets/Scripts/Characters/AutoTargetting.cs @@ -10,37 +10,28 @@ namespace Beyond { public class AutoTargetting : MonoBehaviour { - // ... (other headers and variables) + #region Fields & Properties + [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; [Header("Visuals")] - // ... (visual parameters) public string materialHighlightPropertyName = "_FresnelColor"; - [ColorUsage(true, true)] - public Color highlightColor = Color.white; - [ColorUsage(true, true)] - public Color deselectHighlightColor = Color.black; + [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")] - [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 bool alwaysLockOnInCombat = true; 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 float manualSwitchCooldownDuration = 0.75f; public vFSMBehaviourController CurrentTarget { get; private set; } public event Action OnTargetSelected; @@ -51,14 +42,16 @@ namespace Beyond private Dictionary _originalMaterialColors = new Dictionary(); private Dictionary _materialToFadeCoroutineMap = new Dictionary(); private Transform _playerTransform; - - // Cooldown variables + private bThirdPersonController _playerController; private bool _manualSwitchCooldownActive = false; private float _manualSwitchCooldownTimer = 0f; + #endregion + + #region Unity Methods + void Start() { - // ... (Start logic mostly the same) if (Player.Instance == null) { Debug.LogError("AutoTargetting: Player.Instance is not available at Start!"); @@ -66,6 +59,12 @@ namespace Beyond } _playerTransform = Player.Instance.transform; + _playerController = Player.Instance.GetComponent(); + if (_playerController == null) + { + Debug.LogError("AutoTargetting: Could not find bThirdPersonController on Player.Instance! Custom roll rotation may not work correctly."); + } + _gameStateManager = GameStateManager.Instance; if (_gameStateManager != null) { @@ -80,11 +79,10 @@ namespace Beyond if (targetLockSystem == null) { - if (Player.Instance != null) targetLockSystem = Player.Instance.GetComponentInChildren(true); - if (targetLockSystem == null) targetLockSystem = GetComponent(); // Fallback + targetLockSystem = Player.Instance.GetComponentInChildren(true); if (targetLockSystem == null) { - Debug.LogWarning("AutoTargetting: bLockOn system not found. Auto-lock and target sync will be disabled."); + Debug.LogWarning("AutoTargetting: bLockOn system not found. Auto-lock will be disabled."); autoLockSelectedTarget = false; } } @@ -98,42 +96,42 @@ namespace Beyond void OnDestroy() { - // ... (Cleanup logic) - if (_gameStateManager != null) - { - _gameStateManager.m_OnStateChanged.RemoveListener(HandleGameStateChanged); - } - StopAndClearAllFadeCoroutines(); - if (_targetingLoopCoroutine != null) - { - StopCoroutine(_targetingLoopCoroutine); - _targetingLoopCoroutine = null; - } + if (_gameStateManager != null) _gameStateManager.m_OnStateChanged.RemoveListener(HandleGameStateChanged); + + StopAndClearAllFadeCoroutines(); // This call is correct + if (_targetingLoopCoroutine != null) StopCoroutine(_targetingLoopCoroutine); + if (targetLockSystem != null) { targetLockSystem.onLockOnTarget.RemoveListener(HandleLockOnSystemTargetChanged); targetLockSystem.onUnLockOnTarget.RemoveListener(HandleLockOnSystemTargetUnlocked); } } + + #endregion + + #region Core Logic + + // --- THIS IS THE MISSING METHOD THAT HAS BEEN RESTORED --- private void StopAndClearAllFadeCoroutines() { foreach (var pair in _materialToFadeCoroutineMap) { - if (pair.Value != null) StopCoroutine(pair.Value); + if (pair.Value != null) + { + StopCoroutine(pair.Value); + } } _materialToFadeCoroutineMap.Clear(); } - + // --- END OF RESTORED METHOD --- private void HandleGameStateChanged(GameStateManager.State newState) { if (newState == GameStateManager.State.COMBAT) { - if (_targetingLoopCoroutine == null) - { - _targetingLoopCoroutine = StartCoroutine(TargetingLoop()); - } + if (_targetingLoopCoroutine == null) _targetingLoopCoroutine = StartCoroutine(TargetingLoop()); } else { @@ -142,11 +140,8 @@ namespace Beyond StopCoroutine(_targetingLoopCoroutine); _targetingLoopCoroutine = null; } - if (CurrentTarget != null) - { - SetNewTarget(null, true); // Force immediate update if leaving combat - } - _manualSwitchCooldownActive = false; // Reset cooldown when leaving combat + if (CurrentTarget != null) SetNewTarget(null, true); + _manualSwitchCooldownActive = false; } } @@ -156,48 +151,22 @@ namespace Beyond { if (_manualSwitchCooldownActive) { - _manualSwitchCooldownTimer -= targetingInterval; // Or Time.deltaTime if loop is faster - if (_manualSwitchCooldownTimer <= 0) - { - _manualSwitchCooldownActive = false; - // Debug.Log("AutoTargetting: Manual switch cooldown ended."); - } + _manualSwitchCooldownTimer -= targetingInterval; + if (_manualSwitchCooldownTimer <= 0) _manualSwitchCooldownActive = false; } - if (!_manualSwitchCooldownActive) // Only update target if cooldown is not active + if (!_manualSwitchCooldownActive) { - if (_playerTransform == null && Player.Instance != null) - { - _playerTransform = Player.Instance.transform; - } - - if (_playerTransform != null) - { - UpdateTarget(); - } + 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) - { - // ... (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; - } - - private void UpdateTarget() { - if (_playerTransform == null || _gameStateManager == null) return; - if (_manualSwitchCooldownActive) return; // Double check, though TargetingLoop should handle it + if (_playerTransform == null || _gameStateManager == null || _manualSwitchCooldownActive) return; vFSMBehaviourController bestCandidate = null; float minDistanceSqr = maxTargetingDistance * maxTargetingDistance; @@ -222,161 +191,107 @@ namespace Beyond } } - if (CurrentTarget != bestCandidate) - { - SetNewTarget(bestCandidate); - } - else if (CurrentTarget != null && !IsTargetValid(CurrentTarget)) - { - SetNewTarget(null); - } + 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 (_manualSwitchCooldownActive && !forceLockSystemUpdate) return; + if (CurrentTarget == newTarget && !forceLockSystemUpdate) 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 (targetLockSystem != null) + { + bool shouldLock = (autoLockSelectedTarget || alwaysLockOnInCombat) && CurrentTarget != null; + if (alwaysLockOnInCombat && CurrentTarget != null && !targetLockSystem.isLockingOn) + { + targetLockSystem.SetLockOn(true); + } + targetLockSystem.ManuallySetLockOnTarget(shouldLock ? CurrentTarget.transform : null, true); + } + 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. } } + 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; - // 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); - } + if (CurrentTarget != null && CurrentTarget.transform == lockedTargetTransform) return; + if (fsmTarget != null) SetNewTarget(fsmTarget, true); + else 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 + _manualSwitchCooldownActive = true; _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. } + #endregion - // ... (ExecuteRotationTowardsCurrentTarget, GetCurrentTargetHealth, ClearTarget, Renderer methods, Fade coroutine remain the same) - public void ExecuteRotationTowardsCurrentTarget(float deltaTime) + #region Helper Methods + + public bool IsTargetInAngle(Transform sourceTransform, vFSMBehaviourController targetAI, float angleThreshold) { - 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); + 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 float GetCurrentTargetHealth() @@ -390,33 +305,26 @@ namespace Beyond public void ClearTarget(bool findNewOneImmediately) { - SetNewTarget(null, true); // Force update if clearing target + SetNewTarget(null, true); 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(); + if (!_manualSwitchCooldownActive) + { + UpdateTarget(); + } } } + #endregion + + #region Visuals + 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(); + return targetController.GetComponentsInChildren(true).Distinct().ToArray(); } - private void ApplyHighlight(vFSMBehaviourController targetController, bool fadeIn) { if (targetController == null || string.IsNullOrEmpty(materialHighlightPropertyName)) return; @@ -427,20 +335,11 @@ namespace Beyond foreach (Material mat in rend.materials) { if (mat == null || !mat.HasProperty(materialHighlightPropertyName)) continue; - if (_materialToFadeCoroutineMap.TryGetValue(mat, out Coroutine existingCoroutine) && existingCoroutine != null) - { - StopCoroutine(existingCoroutine); - } + 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; } @@ -454,14 +353,12 @@ namespace Beyond { if (material == null) yield break; timer += Time.deltaTime; - float progress = Mathf.Clamp01(timer / duration); - material.SetColor(materialHighlightPropertyName, Color.Lerp(fromValue, toValue, progress)); + material.SetColor(materialHighlightPropertyName, Color.Lerp(fromValue, toValue, Mathf.Clamp01(timer / duration))); yield return null; } - if (material != null) - { - material.SetColor(materialHighlightPropertyName, toValue); - } + if (material != null) material.SetColor(materialHighlightPropertyName, toValue); } + + #endregion } } \ No newline at end of file diff --git a/Assets/Scripts/InvectorDerivatives/bLockOn.cs b/Assets/Scripts/InvectorDerivatives/bLockOn.cs index 3002aa65e..1312f9283 100644 --- a/Assets/Scripts/InvectorDerivatives/bLockOn.cs +++ b/Assets/Scripts/InvectorDerivatives/bLockOn.cs @@ -165,7 +165,7 @@ namespace Beyond } else if (base.currentTarget == null && isLockingOn) // Target became null while we thought we were locked { - UnlockTarget(false); // Silently unlock if target disappeared + UnlockTarget(false); // Silently unlock if target disappeared } } @@ -394,5 +394,35 @@ namespace Beyond if (Player.Instance != null) _playerTransform = Player.Instance.transform; else _playerTransform = transform; // Fallback if bLockOn is on player } + + // --- ADD THIS ENTIRE METHOD TO YOUR bLockOn.cs SCRIPT --- + + /// + /// Externally sets the lock-on state. + /// + /// True to turn lock-on ON, False to turn it OFF. + public void SetLockOn(bool value) + { + // If the state is already what we want, do nothing. + if (isLockingOn == value) + { + return; + } + + // We don't set isLockingOn directly here, as the methods below handle the state change. + + if (value) + { + // If we are turning lock-on ON, find the best target immediately. + // This prevents a delay before the first lock. + // The `null` argument tells it to find any valid target. + AttemptLockOn(null); + } + else + { + // If we are turning lock-on OFF, clear the current target. + UnlockTarget(); + } + } } } \ No newline at end of file diff --git a/Assets/Scripts/InvectorDerivatives/bThirdPersonController.cs b/Assets/Scripts/InvectorDerivatives/bThirdPersonController.cs index fdb2078d7..5c198910c 100644 --- a/Assets/Scripts/InvectorDerivatives/bThirdPersonController.cs +++ b/Assets/Scripts/InvectorDerivatives/bThirdPersonController.cs @@ -31,6 +31,16 @@ namespace Beyond [Tooltip("The name of the animation state to play when rolling right while strafing.")] public string strafeRollRightAnim = "Roll_Right"; + // --- UPDATED FIELDS FOR INDEPENDENT CONTROL --- + [Header("Beyond's Strafe Roll Correction")] + [Tooltip("The fixed angle to rotate the character before a LEFT strafe roll.")] + [Range(0f, 90f)] + public float strafeRollLeftCorrectionAngle = 45f; + + [Tooltip("The fixed angle to rotate the character before a RIGHT strafe roll.")] + [Range(0f, 90f)] + public float strafeRollRightCorrectionAngle = 70f; + // --- END OF UPDATED FIELDS --- public bool GodMode { @@ -51,7 +61,7 @@ namespace Beyond { base.Start(); } - + protected override void RollBehavior() { // If we are not strafing, use the default Invector roll behavior. @@ -61,17 +71,15 @@ namespace Beyond return; } - // --- Custom Strafe Roll with Root Motion --- + // Custom Strafe Roll with Root Motion (no rotation logic needed here anymore) if (!isRolling) { return; } - // We apply the root motion position change directly. Vector3 deltaPosition = new Vector3(animator.deltaPosition.x, 0f, animator.deltaPosition.z); Vector3 v = (deltaPosition / Time.deltaTime) * (1f - stopMoveWeight); - // Apply gravity to the roll if enabled if (rollUseGravity && animator.GetNormalizedTime(baseLayer) >= rollUseGravityTime) { v.y = _rigidbody.linearVelocity.y; @@ -82,12 +90,11 @@ namespace Beyond public override void Roll() { - // If we are strafing, use our custom directional logic. if (isStrafing) { TriggerStrafeRoll(strafeRollForwardAnim, strafeRollBackwardAnim, strafeRollLeftAnim, strafeRollRightAnim); } - else // Otherwise, use the default free-locomotion roll. + else { OnRoll.Invoke(); isRolling = true; @@ -99,7 +106,6 @@ namespace Beyond public virtual void Dash() { - // The Dash logic now mirrors the Roll logic. if (isStrafing) { TriggerStrafeRoll(strafeRollForwardAnim, strafeRollBackwardAnim, strafeRollLeftAnim, strafeRollRightAnim); @@ -114,7 +120,7 @@ namespace Beyond } } - // This is the private helper method that contains the core logic for this feature. + // --- MODIFIED METHOD WITH TWO-ANGLE LOGIC --- private void TriggerStrafeRoll(string forwardAnim, string backwardAnim, string leftAnim, string rightAnim) { OnRoll.Invoke(); @@ -127,10 +133,30 @@ namespace Beyond // Prioritize side rolls based on horizontal input. if (Mathf.Abs(horizontalSpeed) > strafeRollInputThreshold) { - animToPlay = horizontalSpeed > 0 ? rightAnim : leftAnim; + float correction = 0f; + + // Check if rolling right (positive horizontal speed) + if (horizontalSpeed > 0) + { + animToPlay = rightAnim; + // For a right roll, we apply a negative rotation (turn left) to angle the trajectory forward. + correction = -strafeRollRightCorrectionAngle; + } + // Otherwise, rolling left + else + { + animToPlay = leftAnim; + // For a left roll, we apply a positive rotation (turn right) to angle the trajectory forward. + correction = strafeRollLeftCorrectionAngle; + } + + // Apply the calculated rotation instantly around the Y-axis. + if (correction != 0) + { + transform.Rotate(0, correction, 0); + } } - // If horizontal input is not met, always default to the backward roll. - // This effectively blocks the forward roll. + // If horizontal input is not met, default to the backward roll. else { animToPlay = backwardAnim; @@ -138,6 +164,7 @@ namespace Beyond animator.CrossFadeInFixedTime(animToPlay, rollTransition, baseLayer); } + // --- END OF MODIFIED METHOD --- public void OnEvadeStart() { if (!m_GodMode) isImmortal = true; } public void OnEvadeEnd() { if (!m_GodMode) isImmortal = false; }