attack flow - breaking and blocking input, roll improvements

This commit is contained in:
2025-09-10 12:33:13 +02:00
parent 9d6c237088
commit 058920055b
9 changed files with 1219 additions and 491 deletions

View File

@@ -105633,13 +105633,13 @@ MonoBehaviour:
triggerDieBehaviour: 0 triggerDieBehaviour: 0
m_ignoreTriggers: 1 m_ignoreTriggers: 1
useAnimationBasedRotation: 0 useAnimationBasedRotation: 0
strafeRollInputThreshold: 0.3
strafeRollForwardAnim: Roll_Forward strafeRollForwardAnim: Roll_Forward
strafeRollBackwardAnim: Roll_Backward strafeRollBackwardAnim: Roll_Backward
strafeRollLeftAnim: Roll_Left strafeRollLeftAnim: Roll_Left
strafeRollRightAnim: Roll_Right strafeRollRightAnim: Roll_Right
strafeRollLeftCorrectionAngle: 70.6 strafeRollLeftCorrectionAngle: 70.6
strafeRollRightCorrectionAngle: 70 strafeRollRightCorrectionAngle: 70
strafeRollBackwardCorrectionAngle: 0
--- !u!114 &9202663235077955828 --- !u!114 &9202663235077955828
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -772,8 +772,7 @@ MonoBehaviour:
ignoreDefense: 0 ignoreDefense: 0
activeRagdoll: 0 activeRagdoll: 0
senselessTime: 0 senselessTime: 0
resetAttackTrigger: 1 blockInputBeforeTime: 0.5
resetTriggerBeforeTime: 0.5
unlockRotationTime: 0.5 unlockRotationTime: 0.5
lerpPositionTowardsTarget: 1 lerpPositionTowardsTarget: 1
maxLerpDistance: 3.5 maxLerpDistance: 3.5
@@ -781,8 +780,8 @@ MonoBehaviour:
stoppingDistance: 1.2 stoppingDistance: 1.2
useComboTimingWindow: 1 useComboTimingWindow: 1
comboWindowStartTime: 0.7 comboWindowStartTime: 0.7
comboWindowDuration: 0.4 comboWindowDuration: 0.3
comboWindowTimeScale: 0.2 comboWindowTimeScale: 0.4
useAttackTimeScale: 0 useAttackTimeScale: 0
maxTargetDistance: 3 maxTargetDistance: 3
lowHealthTh: 10 lowHealthTh: 10
@@ -791,7 +790,7 @@ MonoBehaviour:
attackTimeScaleEnd: 0.37 attackTimeScaleEnd: 0.37
rotatePlayerTowardsTarget: 1 rotatePlayerTowardsTarget: 1
degreeThreshold: 100 degreeThreshold: 100
debug: 0 debug: 1
--- !u!1101 &-8051095333111886674 --- !u!1101 &-8051095333111886674
AnimatorStateTransition: AnimatorStateTransition:
m_ObjectHideFlags: 1 m_ObjectHideFlags: 1
@@ -2749,8 +2748,7 @@ MonoBehaviour:
ignoreDefense: 0 ignoreDefense: 0
activeRagdoll: 0 activeRagdoll: 0
senselessTime: 0 senselessTime: 0
resetAttackTrigger: 0 blockInputBeforeTime: 0.5
resetTriggerBeforeTime: 0.3
unlockRotationTime: 0.7 unlockRotationTime: 0.7
lerpPositionTowardsTarget: 1 lerpPositionTowardsTarget: 1
maxLerpDistance: 3.5 maxLerpDistance: 3.5
@@ -2758,8 +2756,8 @@ MonoBehaviour:
stoppingDistance: 1.2 stoppingDistance: 1.2
useComboTimingWindow: 1 useComboTimingWindow: 1
comboWindowStartTime: 0.6 comboWindowStartTime: 0.6
comboWindowDuration: 0.4 comboWindowDuration: 0.3
comboWindowTimeScale: 0.2 comboWindowTimeScale: 0.4
useAttackTimeScale: 1 useAttackTimeScale: 1
maxTargetDistance: 3 maxTargetDistance: 3
lowHealthTh: 30 lowHealthTh: 30
@@ -4567,6 +4565,7 @@ MonoBehaviour:
tags: tags:
- IsRolling - IsRolling
- IgnoreIK - IgnoreIK
- LockRotation
--- !u!206 &-2581317115762456654 --- !u!206 &-2581317115762456654
BlendTree: BlendTree:
m_ObjectHideFlags: 1 m_ObjectHideFlags: 1
@@ -5216,8 +5215,7 @@ MonoBehaviour:
ignoreDefense: 0 ignoreDefense: 0
activeRagdoll: 0 activeRagdoll: 0
senselessTime: 0 senselessTime: 0
resetAttackTrigger: 0 blockInputBeforeTime: 0.5
resetTriggerBeforeTime: 0.3
unlockRotationTime: 0.7 unlockRotationTime: 0.7
lerpPositionTowardsTarget: 1 lerpPositionTowardsTarget: 1
maxLerpDistance: 3.5 maxLerpDistance: 3.5
@@ -5225,8 +5223,8 @@ MonoBehaviour:
stoppingDistance: 1.2 stoppingDistance: 1.2
useComboTimingWindow: 1 useComboTimingWindow: 1
comboWindowStartTime: 0.6 comboWindowStartTime: 0.6
comboWindowDuration: 0.4 comboWindowDuration: 0.3
comboWindowTimeScale: 0.2 comboWindowTimeScale: 0.4
useAttackTimeScale: 1 useAttackTimeScale: 1
maxTargetDistance: 3 maxTargetDistance: 3
lowHealthTh: 10 lowHealthTh: 10
@@ -5738,8 +5736,7 @@ MonoBehaviour:
ignoreDefense: 0 ignoreDefense: 0
activeRagdoll: 0 activeRagdoll: 0
senselessTime: 0 senselessTime: 0
resetAttackTrigger: 0 blockInputBeforeTime: 0.5
resetTriggerBeforeTime: 0.5
unlockRotationTime: 0.7 unlockRotationTime: 0.7
lerpPositionTowardsTarget: 0 lerpPositionTowardsTarget: 0
maxLerpDistance: 3.5 maxLerpDistance: 3.5
@@ -26561,15 +26558,14 @@ MonoBehaviour:
ignoreDefense: 1 ignoreDefense: 1
activeRagdoll: 0 activeRagdoll: 0
senselessTime: 0 senselessTime: 0
resetAttackTrigger: 0 blockInputBeforeTime: 1
resetTriggerBeforeTime: 0.6
unlockRotationTime: 0.7 unlockRotationTime: 0.7
lerpPositionTowardsTarget: 1 lerpPositionTowardsTarget: 1
maxLerpDistance: 3.5 maxLerpDistance: 3.5
positionLerpSpeed: 2 positionLerpSpeed: 2
stoppingDistance: 1.2 stoppingDistance: 1.2
useComboTimingWindow: 0 useComboTimingWindow: 0
comboWindowStartTime: 0.9 comboWindowStartTime: 1
comboWindowDuration: 0.5 comboWindowDuration: 0.5
comboWindowTimeScale: 0.3 comboWindowTimeScale: 0.3
useAttackTimeScale: 0 useAttackTimeScale: 0
@@ -26580,7 +26576,7 @@ MonoBehaviour:
attackTimeScaleEnd: 0.58 attackTimeScaleEnd: 0.58
rotatePlayerTowardsTarget: 1 rotatePlayerTowardsTarget: 1
degreeThreshold: 100 degreeThreshold: 100
debug: 0 debug: 1
--- !u!1101 &542493047717198179 --- !u!1101 &542493047717198179
AnimatorStateTransition: AnimatorStateTransition:
m_ObjectHideFlags: 1 m_ObjectHideFlags: 1
@@ -47984,8 +47980,7 @@ MonoBehaviour:
ignoreDefense: 0 ignoreDefense: 0
activeRagdoll: 0 activeRagdoll: 0
senselessTime: 0 senselessTime: 0
resetAttackTrigger: 0 blockInputBeforeTime: 0.5
resetTriggerBeforeTime: 0.5
unlockRotationTime: 0.7 unlockRotationTime: 0.7
lerpPositionTowardsTarget: 0 lerpPositionTowardsTarget: 0
maxLerpDistance: 3.5 maxLerpDistance: 3.5
@@ -48139,8 +48134,7 @@ MonoBehaviour:
ignoreDefense: 0 ignoreDefense: 0
activeRagdoll: 0 activeRagdoll: 0
senselessTime: 0 senselessTime: 0
resetAttackTrigger: 1 blockInputBeforeTime: 0.5
resetTriggerBeforeTime: 0.5
unlockRotationTime: 0.7 unlockRotationTime: 0.7
lerpPositionTowardsTarget: 0 lerpPositionTowardsTarget: 0
maxLerpDistance: 3.5 maxLerpDistance: 3.5
@@ -48542,8 +48536,7 @@ MonoBehaviour:
ignoreDefense: 1 ignoreDefense: 1
activeRagdoll: 0 activeRagdoll: 0
senselessTime: 0 senselessTime: 0
resetAttackTrigger: 0 blockInputBeforeTime: 0.5
resetTriggerBeforeTime: 0.6
unlockRotationTime: 0.7 unlockRotationTime: 0.7
lerpPositionTowardsTarget: 1 lerpPositionTowardsTarget: 1
maxLerpDistance: 3.5 maxLerpDistance: 3.5
@@ -49431,8 +49424,7 @@ MonoBehaviour:
ignoreDefense: 1 ignoreDefense: 1
activeRagdoll: 0 activeRagdoll: 0
senselessTime: 0 senselessTime: 0
resetAttackTrigger: 0 blockInputBeforeTime: 0.5
resetTriggerBeforeTime: 0.6
unlockRotationTime: 0.7 unlockRotationTime: 0.7
lerpPositionTowardsTarget: 1 lerpPositionTowardsTarget: 1
maxLerpDistance: 3.5 maxLerpDistance: 3.5

View File

@@ -9838,7 +9838,6 @@ GameObject:
- component: {fileID: 7898212980114005163} - component: {fileID: 7898212980114005163}
- component: {fileID: 8020189233111417037} - component: {fileID: 8020189233111417037}
- component: {fileID: 8020706684658151899} - component: {fileID: 8020706684658151899}
- component: {fileID: 7789765392435096873}
m_Layer: 0 m_Layer: 0
m_Name: Particles m_Name: Particles
m_TagString: Untagged m_TagString: Untagged
@@ -14484,21 +14483,6 @@ ParticleSystemRenderer:
m_MeshWeighting2: 1 m_MeshWeighting2: 1
m_MeshWeighting3: 1 m_MeshWeighting3: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
--- !u!114 &7789765392435096873
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7893307381764779335}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dded6759b4b430e4283cf015251490b7, type: 3}
m_Name:
m_EditorClassIdentifier:
target: {fileID: 0}
Force: 1
DistanceRelative: 0
--- !u!1 &7893317824759632779 --- !u!1 &7893317824759632779
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -16396,6 +16396,11 @@ PrefabInstance:
propertyPath: m_Layer propertyPath: m_Layer
value: 5 value: 5
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 1385765897516864307, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3}
propertyPath: openCloseWindow
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1494204689157949844, guid: 851e8e61247888340bdec90fc8aa37f5, - target: {fileID: 1494204689157949844, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3} type: 3}
propertyPath: m_Layer propertyPath: m_Layer
@@ -17420,7 +17425,17 @@ PrefabInstance:
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5, - target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3} type: 3}
propertyPath: strafeRollLeftCorrectionAngle propertyPath: strafeRollLeftCorrectionAngle
value: 70.6 value: -20
objectReference: {fileID: 0}
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3}
propertyPath: strafeRollRightCorrectionAngle
value: -10
objectReference: {fileID: 0}
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3}
propertyPath: strafeRollBackwardCorrectionAngle
value: 180
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5, - target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3} type: 3}
@@ -18565,12 +18580,12 @@ PrefabInstance:
- target: {fileID: 5657452459766331955, guid: 851e8e61247888340bdec90fc8aa37f5, - target: {fileID: 5657452459766331955, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3} type: 3}
propertyPath: m_AnchorMax.x propertyPath: m_AnchorMax.x
value: 1 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 5657452459766331955, guid: 851e8e61247888340bdec90fc8aa37f5, - target: {fileID: 5657452459766331955, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3} type: 3}
propertyPath: m_AnchorMax.y propertyPath: m_AnchorMax.y
value: 1 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 5671630659928648966, guid: 851e8e61247888340bdec90fc8aa37f5, - target: {fileID: 5671630659928648966, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3} type: 3}

View File

@@ -81,8 +81,11 @@ namespace Beyond
private PlayerAttribute brightnessAttribute; private PlayerAttribute brightnessAttribute;
private PlayerAttribute maturityAttribute; private PlayerAttribute maturityAttribute;
private MagicAttacks m_magicAttacks; private MagicAttacks m_magicAttacks;
private bMeleeCombatInput m_meleeCombatInput;
public MagicAttacks Magic => m_magicAttacks; public MagicAttacks Magic => m_magicAttacks;
public bMeleeCombatInput MeleeCombatInput => m_meleeCombatInput;
public PlayerAttribute MaturityAttribute public PlayerAttribute MaturityAttribute
{ {
get get
@@ -186,6 +189,7 @@ namespace Beyond
m_meleeManager = GetComponent<vMeleeManager>(); m_meleeManager = GetComponent<vMeleeManager>();
m_magicAttacks = GetComponent<MagicAttacks>(); m_magicAttacks = GetComponent<MagicAttacks>();
m_autoTargetting = GetComponent<AutoTargetting>(); m_autoTargetting = GetComponent<AutoTargetting>();
m_meleeCombatInput = GetComponent<bMeleeCombatInput>();
} }
@@ -867,10 +871,10 @@ namespace Beyond
public void PlaySingleSound(AudioClip clipToPlay, bool destroyAfterPlaying = true) public void PlaySingleSound(AudioClip clipToPlay, bool destroyAfterPlaying = true)
{ {
// Opcjonalne opóŸnienie na starcie poziomu, aby unikn¹æ "spamowania" dŸwiêkami // Opcjonalne op<EFBFBD>nienie na starcie poziomu, aby unikn<EFBFBD><EFBFBD> "spamowania" d<EFBFBD>wi<EFBFBD>kami
if (Time.timeSinceLevelLoad < 0.5f && clipToPlay != null) if (Time.timeSinceLevelLoad < 0.5f && clipToPlay != null)
{ {
// Mo¿esz chcieæ to odkomentowaæ, jeœli dŸwiêki na starcie s¹ problemem // Mo<EFBFBD>esz chcie<EFBFBD> to odkomentowa<EFBFBD>, je<EFBFBD>li d<EFBFBD>wi<EFBFBD>ki na starcie s<EFBFBD> problemem
// return; // return;
} }
@@ -885,28 +889,28 @@ namespace Beyond
return; return;
} }
// Instancjonowanie prefabu dŸwiêkowego // Instancjonowanie prefabu d<EFBFBD>wi<EFBFBD>kowego
// Upewnij siê, ¿e prefab 'audioSource' ma komponent AudioSource // Upewnij si<EFBFBD>, <EFBFBD>e prefab 'audioSource' ma komponent AudioSource
GameObject audioObjectInstance = Instantiate(this.audioSource, transform.position, transform.rotation); GameObject audioObjectInstance = Instantiate(this.audioSource, transform.position, transform.rotation);
AudioSource sourceComponent = audioObjectInstance.GetComponent<AudioSource>(); AudioSource sourceComponent = audioObjectInstance.GetComponent<AudioSource>();
if (sourceComponent != null) if (sourceComponent != null)
{ {
// PlayOneShot jest dobre dla efektów, nie przerywa innych dŸwiêków na tym samym source, // PlayOneShot jest dobre dla efekt<EFBFBD>w, nie przerywa innych d<EFBFBD>wi<EFBFBD>k<EFBFBD>w na tym samym source,
// jeœli s¹ one odtwarzane przez .Play() i nie u¿ywaj¹ tego samego kana³u. // je<EFBFBD>li s<EFBFBD> one odtwarzane przez .Play() i nie u<EFBFBD>ywaj<EFBFBD> tego samego kana<EFBFBD>u.
sourceComponent.PlayOneShot(clipToPlay); sourceComponent.PlayOneShot(clipToPlay);
if (destroyAfterPlaying) if (destroyAfterPlaying)
{ {
// Niszczymy obiekt GameObject zawieraj¹cy AudioSource po zakoñczeniu odtwarzania klipu. // Niszczymy obiekt GameObject zawieraj<EFBFBD>cy AudioSource po zako<EFBFBD>czeniu odtwarzania klipu.
// Dodajemy ma³y bufor czasowy, aby upewniæ siê, ¿e dŸwiêk zd¹¿y siê odtworzyæ w ca³oœci. // Dodajemy ma<EFBFBD>y bufor czasowy, aby upewni<EFBFBD> si<EFBFBD>, <EFBFBD>e d<EFBFBD>wi<EFBFBD>k zd<EFBFBD><EFBFBD>y si<EFBFBD> odtworzy<EFBFBD> w ca<EFBFBD>o<EFBFBD>ci.
Destroy(audioObjectInstance, clipToPlay.length + 0.1f); Destroy(audioObjectInstance, clipToPlay.length + 0.1f);
} }
} }
else else
{ {
Debug.LogWarning("The instantiated 'audioSource' prefab (from Player.cs) does not have an AudioSource component. Destroying instance."); Debug.LogWarning("The instantiated 'audioSource' prefab (from Player.cs) does not have an AudioSource component. Destroying instance.");
Destroy(audioObjectInstance); // Posprz¹taj, jeœli coœ posz³o nie tak Destroy(audioObjectInstance); // Posprz<EFBFBD>taj, je<EFBFBD>li co<EFBFBD> posz<EFBFBD>o nie tak
} }
} }

View File

@@ -13,6 +13,11 @@ namespace Invector.vMelee
public class bMeleeAttackControl : StateMachineBehaviour public class bMeleeAttackControl : StateMachineBehaviour
{ {
// --- MODIFICATION: Static variable to track the currently active attack state ---
private static int activeAttackInstanceId = 0;
private int myAttackInstanceId;
// --- END MODIFICATION ---
[Header("Damage Window")] [Header("Damage Window")]
[Tooltip("NormalizedTime of Active Damage")] [Tooltip("NormalizedTime of Active Damage")]
public float startDamage = 0.05f; public float startDamage = 0.05f;
@@ -37,10 +42,8 @@ namespace Invector.vMelee
public float senselessTime; public float senselessTime;
[Header("Attack Flow")] [Header("Attack Flow")]
[Tooltip("Check true in the last attack of your combo to reset the FSM attack triggers.")] [Tooltip("Normalized time point to start allowing the next attack input.")]
public bool resetAttackTrigger; public float blockInputBeforeTime = 0.5f;
[Tooltip("Normalized time point to reset attack triggers. (Used only if Combo Timing Window is disabled)")]
public float resetTriggerBeforeTime = 0.5f;
[Header("Combo & Movement")] [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.")] [Tooltip("Normalized time to unlock rotation, allowing the player to aim the next attack in a combo. Set to 1 to disable.")]
@@ -62,7 +65,6 @@ namespace Invector.vMelee
[Tooltip("How close the character should get to the target.")] [Tooltip("How close the character should get to the target.")]
public float stoppingDistance = 1.2f; public float stoppingDistance = 1.2f;
// --- MODIFIED: Switched to a real-time duration model ---
[Header("Combo Timing Window")] [Header("Combo Timing Window")]
[Tooltip("Enable a special timing window at the end of the attack to chain the next combo hit.")] [Tooltip("Enable a special timing window at the end of the attack to chain the next combo hit.")]
public bool useComboTimingWindow = false; public bool useComboTimingWindow = false;
@@ -76,7 +78,6 @@ namespace Invector.vMelee
[Tooltip("The time scale to use during the combo window for a slow-motion effect.")] [Tooltip("The time scale to use during the combo window for a slow-motion effect.")]
public float comboWindowTimeScale = 0.1f; public float comboWindowTimeScale = 0.1f;
// --- END MODIFICATION ---
[Header("Slow Motion Settings")] [Header("Slow Motion Settings")]
[Tooltip("Enable slow motion effect during this attack based on conditions below.")] [Tooltip("Enable slow motion effect during this attack based on conditions below.")]
@@ -104,25 +105,30 @@ namespace Invector.vMelee
private bThirdPersonController _characterController; private bThirdPersonController _characterController;
private bool _isRotationLockedByThis; private bool _isRotationLockedByThis;
private bool _comboWindowEffectTriggered; // --- NEW ---: Tracks if the slow-mo effect has been fired. private bool _comboWindowEffectTriggered;
private Animator _animator; private Animator _animator;
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{ {
if (_animator == null) _animator = animator; if (_animator == null) _animator = animator;
// --- MODIFICATION: Assign a unique ID to this state instance ---
myAttackInstanceId = ++activeAttackInstanceId;
// --- END MODIFICATION ---
mFighter = animator.GetComponent<vIAttackListener>(); mFighter = animator.GetComponent<vIAttackListener>();
_characterController = animator.GetComponent<bThirdPersonController>(); _characterController = animator.GetComponent<bThirdPersonController>();
if (Player.Instance != null) if (Player.Instance != null)
_autoTargettingInstance = Player.Instance.AutoTarget; _autoTargettingInstance = Player.Instance.AutoTarget;
if (_autoTargettingInstance == null && debug) if (_autoTargettingInstance == null && debug)
Debug.LogWarning($"({damageType}) AutoTargetting instance not found. Rotation/Target features limited."); Debug.LogWarning($"({damageType}) AutoTargetting instance not found. Rotation/Target features limited.");
isAttacking = true; isAttacking = true;
isActive = false; isActive = false;
m_hasScaledTime = false; m_hasScaledTime = false;
_comboWindowEffectTriggered = false; // --- NEW ---: Reset the flag on enter. _comboWindowEffectTriggered = false;
if (_characterController != null) if (_characterController != null)
{ {
@@ -134,26 +140,54 @@ namespace Invector.vMelee
mFighter.OnEnableAttack(); mFighter.OnEnableAttack();
if (debug) if (debug)
Debug.Log($"({damageType}) OnStateEnter. The trigger that started this state has been consumed by the Animator."); Debug.Log($"({damageType}, ID: {myAttackInstanceId}) OnStateEnter. Now the authoritative state.");
// --- MODIFICATION: Immediately block input on enter ---
// This ensures the new state takes control right away.
BlockAttack(true);
// --- END MODIFICATION ---
if (attackTimeScaleStart < 0f) attackTimeScaleStart = startDamage; if (attackTimeScaleStart < 0f) attackTimeScaleStart = startDamage;
if (attackTimeScaleEnd < 0f) attackTimeScaleEnd = endDamage; if (attackTimeScaleEnd < 0f) attackTimeScaleEnd = endDamage;
} }
void BlockAttack(bool block)
{
// --- MODIFICATION: Only allow the authoritative state to change the lock ---
if (myAttackInstanceId != activeAttackInstanceId)
{
if(debug) Debug.Log($"({damageType}, ID: {myAttackInstanceId}) Tried to change block but I am not the active instance ({activeAttackInstanceId}). Ignoring.");
return;
}
// --- END MODIFICATION ---
if (Player.Instance != null)
{
var meleeInput = Player.Instance.MeleeCombatInput;
if (meleeInput != null)
{
if (meleeInput.BlockAttack != block) // Only log/change if there is a change
{
if(debug) Debug.Log($"({damageType}, ID: {myAttackInstanceId}) Setting BlockAttack to: {block}");
meleeInput.BlockAttack = block;
}
}
}
}
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{ {
if (Player.Instance.ActiveWeaponTrail) if (Player.Instance.ActiveWeaponTrail)
Player.Instance.ActiveWeaponTrail.m_colorMultiplier = Color.white + Color.red * damageMultiplier; Player.Instance.ActiveWeaponTrail.m_colorMultiplier = Color.white + Color.red * damageMultiplier;
float currentNormalizedTime = stateInfo.normalizedTime % 1; float currentNormalizedTime = stateInfo.normalizedTime % 1;
if (currentNormalizedTime == 0 && stateInfo.normalizedTime > 0.5f) currentNormalizedTime = 1f; if (currentNormalizedTime == 0 && stateInfo.normalizedTime > 0.5f) currentNormalizedTime = 1f;
// --- NEW: Centralized combo logic management ---
ManageComboLogic(currentNormalizedTime); ManageComboLogic(currentNormalizedTime);
if (_characterController != null && _characterController.lockRotation) if (_characterController != null && _characterController.lockRotation)
AttemptRotationTowardsAutoTarget(animator); AttemptRotationTowardsAutoTarget(animator);
AttemptPositionLerp(animator); AttemptPositionLerp(animator);
UpdateRotationLock(currentNormalizedTime); UpdateRotationLock(currentNormalizedTime);
@@ -173,16 +207,16 @@ namespace Invector.vMelee
ActiveDamage(animator, false); ActiveDamage(animator, false);
} }
if (isAttacking && currentNormalizedTime > endDamage) if (isAttacking && currentNormalizedTime > endDamage)
{ {
if (mFighter != null) mFighter.OnDisableAttack(); if (mFighter != null) mFighter.OnDisableAttack();
isAttacking = false; isAttacking = false;
} }
} }
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{ {
if (debug) Debug.Log($"({damageType}) OnStateExit."); if (debug) Debug.Log($"({damageType}, ID: {myAttackInstanceId}) OnStateExit.");
if (isActive) if (isActive)
{ {
@@ -195,68 +229,53 @@ namespace Invector.vMelee
isAttacking = false; isAttacking = false;
m_hasScaledTime = 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}) Final trigger reset on exit due to 'Reset Attack Trigger' flag.");
}
*/
if (_comboWindowEffectTriggered && TimeController.Instance != null) if (_comboWindowEffectTriggered && TimeController.Instance != null)
{ {
_comboWindowEffectTriggered = false; _comboWindowEffectTriggered = false;
TimeController.Instance.Reset(); TimeController.Instance.Reset();
} }
if (_characterController != null && _isRotationLockedByThis) if (_characterController != null && _isRotationLockedByThis)
{ {
_characterController.lockRotation = false; _characterController.lockRotation = false;
_isRotationLockedByThis = false; _isRotationLockedByThis = false;
} }
// --- MODIFICATION: When exiting, ensure the attack is unblocked. ---
// This is a safety net. If this was the last attack in a combo,
// we need to make sure the input is unlocked for future actions.
BlockAttack(false);
// --- END MODIFICATION ---
} }
/// <summary>
/// Manages the combo logic, including input window and time scale effects.
/// </summary>
private void ManageComboLogic(float currentNormalizedTime) private void ManageComboLogic(float currentNormalizedTime)
{ {
if (!useComboTimingWindow) if (blockInputBeforeTime > 0f)
{ {
// Fallback to old logic if new system is disabled for this state if (currentNormalizedTime >= blockInputBeforeTime)
if (resetAttackTrigger && currentNormalizedTime >= resetTriggerBeforeTime)
{ {
if (mFighter != null) BlockAttack(false); // Unlock input
mFighter.ResetAttackTriggers(); }
else
{
BlockAttack(true); // Block input
} }
return;
} }
if (!useComboTimingWindow) return;
// --- FIX 2: Corrected window logic ---
bool isInsideWindow = currentNormalizedTime >= comboWindowStartTime; bool isInsideWindow = currentNormalizedTime >= comboWindowStartTime;
if (isInsideWindow) if (isInsideWindow)
{ {
// --- We are INSIDE the combo window ---
// We STOP resetting the trigger to allow the player's input to register.
if (!_comboWindowEffectTriggered && TimeController.Instance != null) if (!_comboWindowEffectTriggered && TimeController.Instance != null)
{ {
_comboWindowEffectTriggered = true; _comboWindowEffectTriggered = true;
TimeController.Instance.SetTimeScaleForRealTimeSec(comboWindowTimeScale, comboWindowDuration, false); TimeController.Instance.SetTimeScaleForRealTimeSec(comboWindowTimeScale, comboWindowDuration, false);
if (debug) Debug.Log($"({damageType}) COMBO WINDOW OPEN. Accepting input.");
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();
}
} }
// --- Other methods remain unchanged --- // --- Other methods remain unchanged ---

View File

@@ -4,6 +4,7 @@ using Invector.vEventSystems;
using Invector.vMelee; using Invector.vMelee;
using UnityEngine; using UnityEngine;
using System.Collections; using System.Collections;
using UnityEngine.Android;
namespace Beyond namespace Beyond
{ {
@@ -23,6 +24,8 @@ namespace Beyond
internal vMeleeManager meleeManager; internal vMeleeManager meleeManager;
protected bool _isAttacking; protected bool _isAttacking;
public bool BlockAttack { get; set; }
public bool isAttacking public bool isAttacking
{ {
get => _isAttacking || (cc != null && cc.IsAnimatorTag("Attack")); get => _isAttacking || (cc != null && cc.IsAnimatorTag("Attack"));
@@ -87,16 +90,17 @@ namespace Beyond
autoTargeting = GetComponent<AutoTargetting>(); autoTargeting = GetComponent<AutoTargetting>();
if (autoTargeting == null && Player.Instance != null && Player.Instance.gameObject == this.gameObject) if (autoTargeting == null && Player.Instance != null && Player.Instance.gameObject == this.gameObject)
{ {
autoTargeting = Player.Instance.GetComponent<AutoTargetting>(); autoTargeting = Player.Instance.GetComponent<AutoTargetting>();
} }
if (autoTargeting == null && cc != null && cc.transform.root != null) if (autoTargeting == null && cc != null && cc.transform.root != null)
{ {
autoTargeting = cc.transform.root.GetComponentInChildren<AutoTargetting>(true); autoTargeting = cc.transform.root.GetComponentInChildren<AutoTargetting>(true);
} }
if (autoTargeting == null) if (autoTargeting == null)
{ {
Debug.LogWarning("bMeleeCombatInput: AutoTargetting component not found. Dash towards target will require manual aiming."); Debug.LogWarning("bMeleeCombatInput: AutoTargetting component not found. Dash towards target will require manual aiming.");
} }
BlockAttack = false;
} }
protected override void LateUpdate() protected override void LateUpdate()
@@ -241,7 +245,7 @@ namespace Beyond
public virtual bool MeleeAttackConditions() public virtual bool MeleeAttackConditions()
{ {
if (meleeManager == null) meleeManager = GetComponent<vMeleeManager>(); if (meleeManager == null) meleeManager = GetComponent<vMeleeManager>();
return meleeManager != null && cc != null && cc.isGrounded && !cc.customAction && !cc.IsAnimatorTag("special") && return !BlockAttack && meleeManager != null && cc != null && cc.isGrounded && !cc.customAction && !cc.IsAnimatorTag("special") &&
!cc.isJumping && !cc.isCrouching && !cc.isRolling && !isEquipping && !cc.isJumping && !cc.isCrouching && !cc.isRolling && !isEquipping &&
(cc.animator != null && !cc.animator.IsInTransition(cc.baseLayer)) && !isRotatingAndDashing; (cc.animator != null && !cc.animator.IsInTransition(cc.baseLayer)) && !isRotatingAndDashing;
} }

View File

@@ -18,10 +18,6 @@ namespace Beyond
public bool useAnimationBasedRotation = false; public bool useAnimationBasedRotation = false;
[Header("Beyond's Strafe Combat Settings")] [Header("Beyond's Strafe Combat Settings")]
[Tooltip("The minimum horizontal input value to trigger a side roll instead of a forward/backward one.")]
[Range(0.1f, 1.0f)]
public float strafeRollInputThreshold = 0.3f;
[Tooltip("The name of the animation state to play when rolling forward while strafing. (This is now disabled)")] [Tooltip("The name of the animation state to play when rolling forward while strafing. (This is now disabled)")]
public string strafeRollForwardAnim = "Roll_Forward"; // Kept for reference, but won't be used public string strafeRollForwardAnim = "Roll_Forward"; // Kept for reference, but won't be used
[Tooltip("The name of the animation state to play when rolling backward while strafing.")] [Tooltip("The name of the animation state to play when rolling backward while strafing.")]
@@ -31,15 +27,19 @@ namespace Beyond
[Tooltip("The name of the animation state to play when rolling right while strafing.")] [Tooltip("The name of the animation state to play when rolling right while strafing.")]
public string strafeRollRightAnim = "Roll_Right"; public string strafeRollRightAnim = "Roll_Right";
// --- UPDATED FIELDS FOR INDEPENDENT CONTROL --- // --- UPDATED FIELDS FOR FINER CONTROL ---
[Header("Beyond's Strafe Roll Correction")] [Header("Beyond's Strafe Roll Correction")]
[Tooltip("The fixed angle to rotate the character before a LEFT strafe roll.")] [Tooltip("An additional angle to apply for a LEFT strafe roll.")]
[Range(0f, 90f)] [Range(-90f, 90f)]
public float strafeRollLeftCorrectionAngle = 45f; public float strafeRollLeftCorrectionAngle = -45f;
[Tooltip("The fixed angle to rotate the character before a RIGHT strafe roll.")] [Tooltip("An additional angle to apply for a RIGHT strafe roll.")]
[Range(0f, 90f)] [Range(-90f, 90f)]
public float strafeRollRightCorrectionAngle = 70f; public float strafeRollRightCorrectionAngle = 45f;
[Tooltip("An additional angle to apply for a BACKWARD strafe roll. Use 0 for a standard 180-degree roll.")]
[Range(-180f, 180f)]
public float strafeRollBackwardCorrectionAngle = 0f;
// --- END OF UPDATED FIELDS --- // --- END OF UPDATED FIELDS ---
public bool GodMode public bool GodMode
@@ -64,27 +64,21 @@ namespace Beyond
protected override void RollBehavior() protected override void RollBehavior()
{ {
// If we are not strafing, use the default Invector roll behavior.
if (!isStrafing) if (!isStrafing)
{ {
base.RollBehavior(); base.RollBehavior();
return; return;
} }
// Custom Strafe Roll with Root Motion (no rotation logic needed here anymore)
if (!isRolling) if (!isRolling)
{ {
return; return;
} }
Vector3 deltaPosition = new Vector3(animator.deltaPosition.x, 0f, animator.deltaPosition.z); Vector3 deltaPosition = new Vector3(animator.deltaPosition.x, 0f, animator.deltaPosition.z);
Vector3 v = (deltaPosition / Time.deltaTime) * (1f - stopMoveWeight); Vector3 v = (deltaPosition / Time.deltaTime) * (1f - stopMoveWeight);
if (rollUseGravity && animator.GetNormalizedTime(baseLayer) >= rollUseGravityTime) if (rollUseGravity && animator.GetNormalizedTime(baseLayer) >= rollUseGravityTime)
{ {
v.y = _rigidbody.linearVelocity.y; v.y = _rigidbody.linearVelocity.y;
} }
_rigidbody.linearVelocity = v; _rigidbody.linearVelocity = v;
} }
@@ -120,7 +114,12 @@ namespace Beyond
} }
} }
// --- MODIFIED METHOD WITH TWO-ANGLE LOGIC --- // --- FINAL ROBUST METHOD ---
// This logic now handles all specified cases:
// 1. Sets a base rotation facing the camera to ensure consistency.
// 2. Defaults to a backward roll if input is near-zero (fixing the lingering roll issue).
// 3. Never uses the forward roll; it triggers a backward roll instead.
// 4. Applies a unique correction angle for backward rolls as well as side rolls.
private void TriggerStrafeRoll(string forwardAnim, string backwardAnim, string leftAnim, string rightAnim) private void TriggerStrafeRoll(string forwardAnim, string backwardAnim, string leftAnim, string rightAnim)
{ {
OnRoll.Invoke(); OnRoll.Invoke();
@@ -129,42 +128,59 @@ namespace Beyond
currentStaminaRecoveryDelay = 2f; currentStaminaRecoveryDelay = 2f;
string animToPlay; string animToPlay;
float correctionAngle = 0f;
// Prioritize side rolls based on horizontal input. // Determine the base "forward" direction from the camera, ignoring vertical tilt.
if (Mathf.Abs(horizontalSpeed) > strafeRollInputThreshold) Vector3 baseForward = Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized;
if (baseForward == Vector3.zero) baseForward = transform.forward; // Fallback
// Set the character's rotation to this base direction. This provides a clean slate.
transform.rotation = Quaternion.LookRotation(baseForward);
// Check if input is negligible (joystick is centered).
// input.sqrMagnitude is more efficient than input.magnitude. 0.1*0.1=0.01.
if (input.sqrMagnitude < 0.01f)
{ {
float correction = 0f; // If no input, always perform a backward roll.
animToPlay = backwardAnim;
// Check if rolling right (positive horizontal speed) correctionAngle = strafeRollBackwardCorrectionAngle;
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, default to the backward roll.
else else
{ {
animToPlay = backwardAnim; // If there IS input, determine the dominant direction.
if (Mathf.Abs(verticalSpeed) >= Mathf.Abs(horizontalSpeed))
{
// Vertical input is dominant.
// ALWAYS use the backward roll, regardless of forward or backward input.
animToPlay = backwardAnim;
correctionAngle = strafeRollBackwardCorrectionAngle;
}
else
{
// Horizontal input is dominant.
if (horizontalSpeed > 0)
{
animToPlay = rightAnim;
correctionAngle = strafeRollRightCorrectionAngle;
}
else
{
animToPlay = leftAnim;
correctionAngle = strafeRollLeftCorrectionAngle;
}
}
} }
// Apply the chosen correction angle if it's not zero.
if (correctionAngle != 0f)
{
transform.Rotate(0, correctionAngle, 0);
}
// Play the selected animation.
animator.CrossFadeInFixedTime(animToPlay, rollTransition, baseLayer); animator.CrossFadeInFixedTime(animToPlay, rollTransition, baseLayer);
} }
// --- END OF MODIFIED METHOD --- // --- END OF FINAL METHOD ---
public void OnEvadeStart() { if (!m_GodMode) isImmortal = true; } public void OnEvadeStart() { if (!m_GodMode) isImmortal = true; }
public void OnEvadeEnd() { if (!m_GodMode) isImmortal = false; } public void OnEvadeEnd() { if (!m_GodMode) isImmortal = false; }