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"; public string dashHorizontalParam = "DashHorizontal"; public string dashVerticalParam = "DashVertical"; [Tooltip("If your animation is slightly rotated (Red arrow offset), add an angle here to compensate (e.g. -45 or 45).")] public float dashAngleCorrection = 0f; [Tooltip("Print debug info to Console when dashing")] public bool debugDash = true; 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() => m_isDashing || isRolling; protected override void Start() { base.Start(); } public override void Roll() { Dash(); } public virtual void Dash() { OnRoll.Invoke(); isRolling = true; ReduceStamina(rollStamina, false); currentStaminaRecoveryDelay = 2f; // Setup Camera Vectors Transform camT = Camera.main != null ? Camera.main.transform : transform; // Flatten camera vectors so looking up/down doesn't affect dash length Vector3 camFwd = Vector3.Scale(camT.forward, new Vector3(1, 0, 1)).normalized; Vector3 camRight = Vector3.Scale(camT.right, new Vector3(1, 0, 1)).normalized; Vector3 targetWorldDir = Vector3.zero; // 1. Determine Target World Direction if (input.sqrMagnitude < 0.1f) { // NO INPUT -> Move strictly "Into the Screen" (Towards Camera) // This is the negative of the Camera Forward vector. targetWorldDir = -camFwd; if (debugDash) Debug.Log($"[Dash] No Input. Target: BACKWARD ({targetWorldDir})"); } else { // DIRECTIONAL INPUT // FIX: Use input.z for Forward/Backward, NOT input.y targetWorldDir = (camFwd * input.z + camRight * input.x).normalized; if (debugDash) Debug.Log($"[Dash] Input: {input}. Target World Dir: {targetWorldDir}"); } // 2. Convert World Direction to Character Local Space // "Where is the Target World Dir relative to ME?" Vector3 localDir = transform.InverseTransformDirection(targetWorldDir); // 3. Apply Angle Correction (If animation is skewed 45 degrees) if (dashAngleCorrection != 0f) { localDir = Quaternion.Euler(0, dashAngleCorrection, 0) * localDir; } // 4. Normalize (Ensure full blend magnitude) localDir.y = 0; localDir.Normalize(); // 5. Send to Animator (Dedicated Parameters) animator.SetFloat(dashHorizontalParam, localDir.x); animator.SetFloat(dashVerticalParam, localDir.z); if (debugDash) { Debug.Log($"[Dash Final] Local X: {localDir.x} | Local Z: {localDir.z}"); } // 6. Play Animation animator.CrossFadeInFixedTime(dashBlendTreeState, rollTransition, baseLayer); } // --- Standard Boilerplate 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 && !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 && !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); } } } }