diff --git a/Assets/Prefabs/Characters/Player/Animator/Invector@MeleeCombat Player PRO2.controller b/Assets/Prefabs/Characters/Player/Animator/Invector@MeleeCombat Player PRO2.controller index 28dc820dc..2a00527fe 100644 --- a/Assets/Prefabs/Characters/Player/Animator/Invector@MeleeCombat Player PRO2.controller +++ b/Assets/Prefabs/Characters/Player/Animator/Invector@MeleeCombat Player PRO2.controller @@ -732,13 +732,17 @@ MonoBehaviour: ignoreDefense: 0 activeRagdoll: 0 senselessTime: 0 - resetAttackTrigger: 0 - resetTriggerBeforeTime: 0.3 - unlockRotationTime: 0.7 + resetAttackTrigger: 1 + resetTriggerBeforeTime: 0.5 + unlockRotationTime: 0.5 lerpPositionTowardsTarget: 1 maxLerpDistance: 3.5 positionLerpSpeed: 2 stoppingDistance: 1.2 + useComboTimingWindow: 1 + comboWindowStartTime: 0.5 + comboWindowDuration: 0.2 + comboWindowTimeScale: 0.3 useAttackTimeScale: 0 maxTargetDistance: 3 lowHealthTh: 10 @@ -747,7 +751,7 @@ MonoBehaviour: attackTimeScaleEnd: 0.37 rotatePlayerTowardsTarget: 1 degreeThreshold: 100 - debug: 0 + debug: 1 --- !u!1101 &-8051095333111886674 AnimatorStateTransition: m_ObjectHideFlags: 1 @@ -2694,7 +2698,11 @@ MonoBehaviour: maxLerpDistance: 3.5 positionLerpSpeed: 2 stoppingDistance: 1.2 - useAttackTimeScale: 0 + useComboTimingWindow: 1 + comboWindowStartTime: 0.6 + comboWindowDuration: 0.2 + comboWindowTimeScale: 0.3 + useAttackTimeScale: 1 maxTargetDistance: 3 lowHealthTh: 30 attackTimeScale: 0.1 @@ -2702,7 +2710,7 @@ MonoBehaviour: attackTimeScaleEnd: 0.5 rotatePlayerTowardsTarget: 1 degreeThreshold: 100 - debug: 0 + debug: 1 --- !u!114 &-4885966736990036633 MonoBehaviour: m_ObjectHideFlags: 1 @@ -5045,7 +5053,11 @@ MonoBehaviour: maxLerpDistance: 3.5 positionLerpSpeed: 2 stoppingDistance: 1.2 - useAttackTimeScale: 0 + useComboTimingWindow: 1 + comboWindowStartTime: 0.6 + comboWindowDuration: 0.2 + comboWindowTimeScale: 0.3 + useAttackTimeScale: 1 maxTargetDistance: 3 lowHealthTh: 10 attackTimeScale: 0.1 @@ -5053,7 +5065,7 @@ MonoBehaviour: attackTimeScaleEnd: 0.3 rotatePlayerTowardsTarget: 1 degreeThreshold: 100 - debug: 0 + debug: 1 --- !u!114 &-1270803735202051326 MonoBehaviour: m_ObjectHideFlags: 1 @@ -5541,6 +5553,10 @@ MonoBehaviour: maxLerpDistance: 3.5 positionLerpSpeed: 2 stoppingDistance: 1.2 + useComboTimingWindow: 0 + comboWindowStartTime: 0.9 + comboWindowDuration: 0.5 + comboWindowTimeScale: 0.3 useAttackTimeScale: 0 maxTargetDistance: 3 lowHealthTh: 10 @@ -26299,6 +26315,10 @@ MonoBehaviour: maxLerpDistance: 3.5 positionLerpSpeed: 2 stoppingDistance: 1.2 + useComboTimingWindow: 0 + comboWindowStartTime: 0.9 + comboWindowDuration: 0.5 + comboWindowTimeScale: 0.3 useAttackTimeScale: 0 maxTargetDistance: 3 lowHealthTh: 35 @@ -47667,6 +47687,10 @@ MonoBehaviour: maxLerpDistance: 3.5 positionLerpSpeed: 2 stoppingDistance: 1.2 + useComboTimingWindow: 0 + comboWindowStartTime: 0.9 + comboWindowDuration: 0.5 + comboWindowTimeScale: 0.3 useAttackTimeScale: 0 maxTargetDistance: 3 lowHealthTh: 10 @@ -47818,6 +47842,10 @@ MonoBehaviour: maxLerpDistance: 3.5 positionLerpSpeed: 2 stoppingDistance: 1.2 + useComboTimingWindow: 0 + comboWindowStartTime: 0.9 + comboWindowDuration: 0.5 + comboWindowTimeScale: 0.3 useAttackTimeScale: 0 maxTargetDistance: 3 lowHealthTh: 10 diff --git a/Assets/Scripts/InvectorDerivatives/bMeleeAttackControl.cs b/Assets/Scripts/InvectorDerivatives/bMeleeAttackControl.cs index 1f718c551..6c7d78093 100644 --- a/Assets/Scripts/InvectorDerivatives/bMeleeAttackControl.cs +++ b/Assets/Scripts/InvectorDerivatives/bMeleeAttackControl.cs @@ -39,10 +39,9 @@ namespace Invector.vMelee [Header("Attack Flow")] [Tooltip("Check true in the last attack of your combo to reset the FSM attack triggers.")] public bool resetAttackTrigger; - [Tooltip("Normalized time point to reset attack triggers if resetAttackTrigger is true.")] + [Tooltip("Normalized time point to reset attack triggers. (Used only if Combo Timing Window is disabled)")] public float resetTriggerBeforeTime = 0.5f; - // --- NEW ---: Combo Rotation and Position Lerp Settings [Header("Combo & Movement")] [Tooltip("Normalized time to unlock rotation, allowing the player to aim the next attack in a combo. Set to 1 to disable.")] [Range(0,1)] @@ -62,7 +61,22 @@ namespace Invector.vMelee [vHideInInspector("lerpPositionTowardsTarget")] [Tooltip("How close the character should get to the target.")] public float stoppingDistance = 1.2f; - // --- END NEW --- + + // --- MODIFIED: Switched to a real-time duration model --- + [Header("Combo Timing Window")] + [Tooltip("Enable a special timing window at the end of the attack to chain the next combo hit.")] + public bool useComboTimingWindow = false; + + [Tooltip("Normalized time to START the combo window.")] + [Range(0, 1)] + public float comboWindowStartTime = 0.8f; + + [Tooltip("How long the combo window stays open in REAL-WORLD SECONDS.")] + public float comboWindowDuration = 0.5f; + + [Tooltip("The time scale to use during the combo window for a slow-motion effect.")] + public float comboWindowTimeScale = 0.1f; + // --- END MODIFICATION --- [Header("Slow Motion Settings")] [Tooltip("Enable slow motion effect during this attack based on conditions below.")] @@ -88,46 +102,39 @@ namespace Invector.vMelee private bool m_hasScaledTime; private AutoTargetting _autoTargettingInstance; - // --- NEW ---: Private variables for new features private bThirdPersonController _characterController; - private bool _isRotationLockedByThis; // Tracks if this specific state has locked rotation - // --- END NEW --- - + private bool _isRotationLockedByThis; + private bool _comboWindowEffectTriggered; // --- NEW ---: Tracks if the slow-mo effect has been fired. + private Animator _animator; override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { + if (_animator == null) _animator = animator; + mFighter = animator.GetComponent(); - // --- NEW ---: Get reference to the character controller _characterController = animator.GetComponent(); - // --- END NEW --- if (Player.Instance != null) - { _autoTargettingInstance = Player.Instance.AutoTarget; - } if (_autoTargettingInstance == null && debug) - { - Debug.LogWarning($"({damageType}) AutoTargetting instance not found on {animator.name}. Rotation and target-dependent features will be limited."); - } + Debug.LogWarning($"({damageType}) AutoTargetting instance not found. Rotation/Target features limited."); isAttacking = true; isActive = false; m_hasScaledTime = false; - - // --- NEW ---: Lock character rotation at the beginning of the attack + _comboWindowEffectTriggered = false; // --- NEW ---: Reset the flag on enter. + if (_characterController != null) { _characterController.lockRotation = true; _isRotationLockedByThis = true; - if (debug) Debug.Log($"({damageType}) Rotation locked by state."); } - // --- END NEW --- if (mFighter != null) mFighter.OnEnableAttack(); if (debug) - Debug.Log($"({damageType}) OnStateEnter: {animator.name}, Layer: {layerIndex}"); + Debug.Log($"({damageType}) OnStateEnter. The trigger that started this state has been consumed by the Animator."); if (attackTimeScaleStart < 0f) attackTimeScaleStart = startDamage; if (attackTimeScaleEnd < 0f) attackTimeScaleEnd = endDamage; @@ -136,208 +143,187 @@ namespace Invector.vMelee override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { if (Player.Instance.ActiveWeaponTrail) - { Player.Instance.ActiveWeaponTrail.m_colorMultiplier = Color.white + Color.red * damageMultiplier; - } float currentNormalizedTime = stateInfo.normalizedTime % 1; if (currentNormalizedTime == 0 && stateInfo.normalizedTime > 0.5f) currentNormalizedTime = 1f; - // --- MODIFIED ---: Rotation and Movement logic is now more sophisticated - // Only execute script-based rotation if the controller's rotation is locked - if (_characterController != null && _characterController.lockRotation) - { - AttemptRotationTowardsAutoTarget(animator); - } + // --- NEW: Centralized combo logic management --- + ManageComboLogic(currentNormalizedTime); + + if (_characterController != null && _characterController.lockRotation) + AttemptRotationTowardsAutoTarget(animator); - // Handle position lerping AttemptPositionLerp(animator); - - // Handle unlocking rotation for combo aiming UpdateRotationLock(currentNormalizedTime); - // --- END MODIFIED --- if (useAttackTimeScale) - { UpdateSlowMotion(animator, stateInfo, currentNormalizedTime); - } if (!isActive && currentNormalizedTime >= startDamage && currentNormalizedTime <= endDamage) { - if (debug) Debug.Log($"({damageType}) Enable Damage: normTime={currentNormalizedTime:F2} (Start:{startDamage:F2}, End:{endDamage:F2})"); + if (debug) Debug.Log($"({damageType}) Enable Damage: normTime={currentNormalizedTime:F2}"); isActive = true; ActiveDamage(animator, true); } else if (isActive && currentNormalizedTime > endDamage) { - if (debug) Debug.Log($"({damageType}) Disable Damage: normTime={currentNormalizedTime:F2} > {endDamage:F2}"); + if (debug) Debug.Log($"({damageType}) Disable Damage: normTime={currentNormalizedTime:F2}"); isActive = false; ActiveDamage(animator, false); } - if (isAttacking) + if (isAttacking && currentNormalizedTime > endDamage) { - if (currentNormalizedTime > endDamage) - { - if (mFighter != null) mFighter.OnDisableAttack(); - isAttacking = false; - if (debug) Debug.Log($"({damageType}) OnDisableAttack called: normTime={currentNormalizedTime:F2}"); - } - else if (resetAttackTrigger && currentNormalizedTime >= resetTriggerBeforeTime) - { - if (mFighter != null) mFighter.ResetAttackTriggers(); - if (debug) Debug.Log($"({damageType}) ResetAttackTriggers called: normTime={currentNormalizedTime:F2}"); - } + if (mFighter != null) mFighter.OnDisableAttack(); + isAttacking = false; } } override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { - if (debug) Debug.Log($"({damageType}) OnStateExit: {animator.name}"); + if (debug) Debug.Log($"({damageType}) OnStateExit."); if (isActive) { isActive = false; ActiveDamage(animator, false); - if (debug) Debug.Log($"({damageType}) Damage disabled on StateExit."); } if (isAttacking && mFighter != null) - { mFighter.OnDisableAttack(); - if (debug) Debug.Log($"({damageType}) OnDisableAttack called on StateExit (fallback)."); - } - isAttacking = false; + isAttacking = false; m_hasScaledTime = false; + // The TimeController now reliably handles resetting the time scale via its own coroutine. + // A manual reset here is no longer necessary and could cause conflicts. +/* if (mFighter != null && resetAttackTrigger) { mFighter.ResetAttackTriggers(); - if (debug) Debug.Log($"({damageType}) ResetAttackTriggers called on StateExit due to resetAttackTrigger flag."); + if (debug) Debug.Log($"({damageType}) Final trigger reset on exit due to 'Reset Attack Trigger' flag."); } - - // --- NEW ---: Ensure rotation is unlocked upon exiting the state +*/ if (_characterController != null && _isRotationLockedByThis) { _characterController.lockRotation = false; _isRotationLockedByThis = false; - if (debug) Debug.Log($"({damageType}) Rotation unlocked by state on exit."); } - // --- END NEW --- } - - private void AttemptRotationTowardsAutoTarget(Animator animator) + + /// + /// Manages the combo logic, including input window and time scale effects. + /// + private void ManageComboLogic(float currentNormalizedTime) { - if (!rotatePlayerTowardsTarget || _autoTargettingInstance == null || _autoTargettingInstance.CurrentTarget == null) + if (!useComboTimingWindow) { + // Fallback to old logic if new system is disabled for this state + if (resetAttackTrigger && currentNormalizedTime >= resetTriggerBeforeTime) + { + if (mFighter != null) + mFighter.ResetAttackTriggers(); + } return; } - if (_autoTargettingInstance.IsTargetInAngle(animator.transform, _autoTargettingInstance.CurrentTarget, degreeThreshold)) + // --- FIX 2: Corrected window logic --- + bool isInsideWindow = currentNormalizedTime >= comboWindowStartTime; + + if (isInsideWindow) { - _autoTargettingInstance.ExecuteRotationTowardsCurrentTarget(Time.deltaTime); + // --- We are INSIDE the combo window --- + // We STOP resetting the trigger to allow the player's input to register. + + if (!_comboWindowEffectTriggered && TimeController.Instance != null) + { + _comboWindowEffectTriggered = true; + + TimeController.Instance.SetTimeScaleForRealTimeSec(comboWindowTimeScale, comboWindowDuration, false); + + if (debug) Debug.Log($"({damageType}) COMBO WINDOW OPEN. Accepting input. Animator mode set to UnscaledTime."); + } + } + else + { + // --- We are OUTSIDE the combo window --- + // We continuously reset the trigger to consume any premature/late input. + if (mFighter != null) + mFighter.ResetAttackTriggers(); } } - // --- NEW --- + // --- Other methods remain unchanged --- + + private void AttemptRotationTowardsAutoTarget(Animator animator) + { + if (!rotatePlayerTowardsTarget || _autoTargettingInstance == null || _autoTargettingInstance.CurrentTarget == null) return; + if (_autoTargettingInstance.IsTargetInAngle(animator.transform, _autoTargettingInstance.CurrentTarget, degreeThreshold)) + _autoTargettingInstance.ExecuteRotationTowardsCurrentTarget(Time.deltaTime); + } + private void UpdateRotationLock(float normalizedTime) { if (_characterController != null && _isRotationLockedByThis && normalizedTime >= unlockRotationTime) { _characterController.lockRotation = false; - _isRotationLockedByThis = false; // Stop this state from managing the lock - if(debug) Debug.Log($"({damageType}) Rotation unlocked for combo aiming at normTime={normalizedTime:F2}"); + _isRotationLockedByThis = false; } } private void AttemptPositionLerp(Animator animator) { - if (!lerpPositionTowardsTarget || _characterController == null || _autoTargettingInstance == null || _autoTargettingInstance.CurrentTarget == null) - { - return; - } + if (!lerpPositionTowardsTarget || _characterController == null || _autoTargettingInstance == null || _autoTargettingInstance.CurrentTarget == null) return; Transform playerTransform = _characterController.transform; Transform targetTransform = _autoTargettingInstance.CurrentTarget.transform; - float distance = Vector3.Distance(playerTransform.position, targetTransform.position); - // Only lerp if within max distance and further than stopping distance if (distance <= maxLerpDistance && distance > stoppingDistance) { Vector3 directionToTarget = (targetTransform.position - playerTransform.position).normalized; - directionToTarget.y = 0; // Keep movement on the horizontal plane - - // The target position is a point in front of the enemy at the stopping distance + directionToTarget.y = 0; Vector3 targetPosition = targetTransform.position - directionToTarget * stoppingDistance; - - // Use MoveTowards for consistent speed. This adds to root motion rather than fighting it. playerTransform.position = Vector3.MoveTowards(playerTransform.position, targetPosition, positionLerpSpeed * Time.deltaTime); } } - // --- END NEW --- private void UpdateSlowMotion(Animator animator, AnimatorStateInfo stateInfo, float currentNormalizedTime) { - // ... (this method remains unchanged) if (_autoTargettingInstance == null || TimeController.Instance == null) return; - if (!m_hasScaledTime) + if (!m_hasScaledTime && currentNormalizedTime >= attackTimeScaleStart && currentNormalizedTime <= attackTimeScaleEnd) { - if (currentNormalizedTime >= attackTimeScaleStart && currentNormalizedTime <= attackTimeScaleEnd) + bool triggerSlowMo = false; + if (_autoTargettingInstance.CurrentTarget != null) { - bool triggerSlowMo = false; - if (_autoTargettingInstance.CurrentTarget != null) + float distSqr = (_autoTargettingInstance.CurrentTarget.transform.position - animator.transform.position).sqrMagnitude; + if (distSqr <= maxTargetDistance * maxTargetDistance) { - float distSqr = (_autoTargettingInstance.CurrentTarget.transform.position - animator.transform.position).sqrMagnitude; - bool targetNear = distSqr <= maxTargetDistance * maxTargetDistance; - if (targetNear) - { - float currentTargetHealth = _autoTargettingInstance.GetCurrentTargetHealth(); - bool targetHealthLow = currentTargetHealth > 0f && currentTargetHealth < lowHealthTh; - if (targetHealthLow) - { - triggerSlowMo = true; - } - else - { - triggerSlowMo = true; - } - } + triggerSlowMo = true; } - if (triggerSlowMo) + } + if (triggerSlowMo) + { + float slowMoEffectDuration = (attackTimeScaleEnd - currentNormalizedTime) * stateInfo.length; + if (slowMoEffectDuration > 0.01f) { - float slowMoEffectDuration = (attackTimeScaleEnd - currentNormalizedTime) * stateInfo.length; - if (slowMoEffectDuration > 0.01f) - { - TimeController.Instance.SetTimeScaleForSec(attackTimeScale, slowMoEffectDuration); - if (debug) Debug.Log($"({damageType}) Slow-mo ACTIVATED. Target: {_autoTargettingInstance.CurrentTarget?.name ?? "N/A"}. Duration: {slowMoEffectDuration:F2}s. NormTime: {currentNormalizedTime:F2}"); - } - else if (debug) Debug.Log($"({damageType}) Slow-mo trigger met, but calculated duration too short ({slowMoEffectDuration:F2}s). NormTime: {currentNormalizedTime:F2}"); + TimeController.Instance.SetTimeScaleForSec(attackTimeScale, slowMoEffectDuration); m_hasScaledTime = true; } } } - else if (currentNormalizedTime > attackTimeScaleEnd && m_hasScaledTime) + else if (m_hasScaledTime && currentNormalizedTime > attackTimeScaleEnd) { m_hasScaledTime = false; - if (debug) Debug.Log($"({damageType}) Slow-mo window ended (normTime={currentNormalizedTime:F2}). m_hasScaledTime reset."); } } void ActiveDamage(Animator animator, bool value) { - // ... (this method remains unchanged) var meleeManager = animator.GetComponent(); if (meleeManager) - { - meleeManager.SetActiveAttack(bodyParts, meleeAttackType, value, damageMultiplier, recoilID, reactionID, - ignoreDefense, activeRagdoll, senselessTime, damageType); - } - else if(debug) - { - Debug.LogWarning($"({damageType}) vMeleeManager not found on {animator.name}. Cannot activate/deactivate damage."); - } + meleeManager.SetActiveAttack(bodyParts, meleeAttackType, value, damageMultiplier, recoilID, reactionID, ignoreDefense, activeRagdoll, senselessTime, damageType); } } } \ No newline at end of file diff --git a/Assets/Scripts/Utils/TimeController.cs b/Assets/Scripts/Utils/TimeController.cs index 0eece7af9..5c82ae2fb 100644 --- a/Assets/Scripts/Utils/TimeController.cs +++ b/Assets/Scripts/Utils/TimeController.cs @@ -10,7 +10,7 @@ namespace Beyond public UnityEvent m_OnTimeScaleChanged; - private float m_startingFixedDelta; + private float m_startingFixedDelta = 0.02f; private float m_currentTimeScale = 1f; public void SetTimeScale(float s) @@ -32,7 +32,7 @@ namespace Beyond StopAllCoroutines(); SetTimeScale(1f); } - + [Sirenix.OdinInspector.Button] public void SetTimeScaleForRealTimeSec(float s, float seconds, bool forceStop = false) { if (forceStop) @@ -58,19 +58,8 @@ namespace Beyond protected override void Awake() { base.Awake(); - m_startingFixedDelta = Time.fixedDeltaTime; + //m_startingFixedDelta = Time.fixedDeltaTime; } - // Start is called before the first frame update - void Start() - { - - } - - // Update is called once per frame - void Update() - { - - } } } diff --git a/ProjectSettings/TimeManager.asset b/ProjectSettings/TimeManager.asset index 558a017e1..2e23a1f42 100644 --- a/ProjectSettings/TimeManager.asset +++ b/ProjectSettings/TimeManager.asset @@ -3,7 +3,12 @@ --- !u!5 &1 TimeManager: m_ObjectHideFlags: 0 - Fixed Timestep: 0.02 + serializedVersion: 2 + Fixed Timestep: + m_Count: 2822399 + m_Rate: + m_Denominator: 1 + m_Numerator: 141120000 Maximum Allowed Timestep: 0.33333334 m_TimeScale: 1 Maximum Particle Timestep: 0.03