using System.Collections; using System.Collections.Generic; using Invector; using Invector.vCharacterController; using Invector.vEventSystems; using UnityEngine; namespace Beyond { public class bThirdPersonController : vThirdPersonController { protected bool m_isDashing; protected bool m_GodMode = false; public bool m_ignoreTriggers = true; [Header("Beyond Health Logic")] public float healthRecoveryCap = 1f; [Header("Beyond's Custom Settings")] public bool useAnimationBasedRotation = false; [Header("Beyond's Dash Settings")] public string dashBlendTreeState = "Dash_Directional"; // Internal flag to lock animator parameters during dash private bool _lockAnimParamsForDash = false; public bool GodMode { get => m_GodMode; set { m_GodMode = value; isImmortal = m_GodMode; } } protected override bool canRecoverHealth { get { float limitHP = maxHealth * healthRecoveryCap; return base.canRecoverHealth && (_currentHealth < limitHP); } } public bool IsDashingOrRolling() { return m_isDashing || isRolling; } protected override void Start() { base.Start(); } protected override void RollBehavior() { base.RollBehavior(); } public override void Roll() { Dash(); } public virtual void Dash() { OnRoll.Invoke(); isRolling = true; ReduceStamina(rollStamina, false); currentStaminaRecoveryDelay = 2f; // 1. Handle Stationary (Backwards Dash) if (input.sqrMagnitude < 0.05f) { ApplyDashParams(0f, -1f); } else { // 2. Handle Directional Input (Camera Relative -> Character Relative) // A. Get the Input Direction relative to the Camera Vector3 inputDir = Vector3.zero; if (Camera.main != null) { // Convert joystick input (X,Y) into World Space based on Camera facing Vector3 camFwd = Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized; Vector3 camRight = Vector3.Scale(Camera.main.transform.right, new Vector3(1, 0, 1)).normalized; inputDir = (camFwd * input.y + camRight * input.x).normalized; } else { // Fallback if no camera found inputDir = new Vector3(input.x, 0, input.y).normalized; } // B. Convert that World Direction into the Character's Local Space // This tells us: "Is the input to the Left, Right, or Forward of ME?" Vector3 localDir = transform.InverseTransformDirection(inputDir); // C. Send to Animator ApplyDashParams(localDir.x, localDir.z); } // 3. Play Animation animator.CrossFadeInFixedTime(dashBlendTreeState, rollTransition, baseLayer); // 4. Lock parameters briefly so Invector doesn't overwrite them immediately StartCoroutine(LockDashParamsRoutine()); } // Helper to set params and lock the update loop private void ApplyDashParams(float x, float y) { _lockAnimParamsForDash = true; animator.SetFloat(vAnimatorParameters.InputHorizontal, x); animator.SetFloat(vAnimatorParameters.InputVertical, y); } // Release the lock after a short delay (once the blend has firmly started) private IEnumerator LockDashParamsRoutine() { yield return new WaitForSeconds(0.2f); // Adjust this if the blend pops back too soon _lockAnimParamsForDash = false; } // --- CRITICAL FIX: Override Invector's Parameter Update --- public override void UpdateAnimatorParameters() { // If we are locking parameters for the dash, DO NOT let the base class overwrite them. if (_lockAnimParamsForDash) return; // Otherwise, run standard Invector logic base.UpdateAnimatorParameters(); } // Standard overrides below... public void OnEvadeStart() { if (!m_GodMode) isImmortal = true; } public void OnEvadeEnd() { if (!m_GodMode) isImmortal = false; } public override void ActionsControl() { base.ActionsControl(); m_isDashing = IsAnimatorTag("IsDashing"); } protected override void DeadAnimation() { if (!isDead) return; if (!triggerDieBehaviour) { triggerDieBehaviour = true; DeathBehaviour(); } if (deathBy == DeathBy.Animation) { int deadLayer = 0; var info = animatorStateInfos.GetStateInfoUsingTag("Dead"); if (info != null) { if (!animator.IsInTransition(deadLayer) && info.normalizedTime >= 0.99f && groundDistance <= 0.15f) RemoveComponents(); } } else if (deathBy == DeathBy.AnimationWithRagdoll) { int deadLayer = 0; var info = animatorStateInfos.GetStateInfoUsingTag("Dead"); if (info != null) { if (!animator.IsInTransition(deadLayer) && info.normalizedTime >= 0.8f) onActiveRagdoll.Invoke(null); } } else if (deathBy == DeathBy.Ragdoll) { onActiveRagdoll.Invoke(null); } } public void RemoveAnimatorTags() { animatorStateInfos.stateInfos.vToList().ForEach(infos => infos.tags.Clear()); } public override void ControlAnimatorRootMotion() { if (!this.enabled) return; if (isRolling) { RollBehavior(); return; } if (customAction || lockAnimMovement) { StopCharacterWithLerp(); transform.position = animator.rootPosition; transform.rotation = animator.rootRotation; } else if (IsAnimatorTag("Attack")) { if (lockRotation) StopCharacterWithLerp(); transform.position = animator.rootPosition; } if (useRootMotion) MoveCharacter(moveDirection); } protected override void OnTriggerEnter(Collider other) { if (!m_ignoreTriggers) onActionEnter.Invoke(other); } protected override void OnTriggerStay(Collider other) { try { CheckForAutoCrouch(other); } catch (UnityException e) { Debug.LogWarning(e.Message); } if (!m_ignoreTriggers) base.OnTriggerStay(other); } protected override void OnTriggerExit(Collider other) { AutoCrouchExit(other); if (!m_ignoreTriggers) base.OnTriggerExit(other); } void DrawWeaponLowLeft() { } public override void ControlRotationType() { if (lockAnimRotation || lockRotation || customAction || isRolling) return; bool validInput = input != Vector3.zero || (isStrafing ? strafeSpeed.rotateWithCamera : freeSpeed.rotateWithCamera); if (validInput) { if (lockAnimMovement) { inputSmooth = Vector3.Lerp(inputSmooth, input, (isStrafing ? strafeSpeed.movementSmooth : freeSpeed.movementSmooth) * Time.deltaTime); } Vector3 dir = (isStrafing && isGrounded && (!isSprinting || sprintOnlyFree == false) || (freeSpeed.rotateWithCamera && input == Vector3.zero)) && rotateTarget ? rotateTarget.forward : moveDirection; if (isStrafing || !useAnimationBasedRotation) RotateToDirection(dir); } } } }