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's Custom Settings")] [Tooltip("When 'Use RootMotion' is checked, and this is true, the animation's root motion will control character rotation (for 8-way directional movement). If false, the script will rotate the character to face the input direction.")] public bool useAnimationBasedRotation = false; [Header("Beyond's Strafe Combat Settings")] [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 [Tooltip("The name of the animation state to play when rolling backward while strafing.")] public string strafeRollBackwardAnim = "Roll_Backward"; [Tooltip("The name of the animation state to play when rolling left while strafing.")] public string strafeRollLeftAnim = "Roll_Left"; [Tooltip("The name of the animation state to play when rolling right while strafing.")] public string strafeRollRightAnim = "Roll_Right"; // --- UPDATED FIELDS FOR FINER CONTROL --- [Header("Beyond's Strafe Roll Correction")] [Tooltip("An additional angle to apply for a LEFT strafe roll.")] [Range(-90f, 90f)] public float strafeRollLeftCorrectionAngle = -45f; [Tooltip("An additional angle to apply for a RIGHT strafe roll.")] [Range(-90f, 90f)] 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 --- public bool GodMode { get => m_GodMode; set { m_GodMode = value; isImmortal = m_GodMode; } } public bool IsDashingOrRolling() { return m_isDashing || isRolling; } protected override void Start() { base.Start(); } protected override void RollBehavior() { if (!isStrafing) { base.RollBehavior(); return; } if (!isRolling) { return; } Vector3 deltaPosition = new Vector3(animator.deltaPosition.x, 0f, animator.deltaPosition.z); Vector3 v = (deltaPosition / Time.deltaTime) * (1f - stopMoveWeight); if (rollUseGravity && animator.GetNormalizedTime(baseLayer) >= rollUseGravityTime) { v.y = _rigidbody.linearVelocity.y; } _rigidbody.linearVelocity = v; } public override void Roll() { if (isStrafing) { TriggerStrafeRoll(strafeRollForwardAnim, strafeRollBackwardAnim, strafeRollLeftAnim, strafeRollRightAnim); } else { OnRoll.Invoke(); isRolling = true; animator.CrossFadeInFixedTime("Roll", rollTransition, baseLayer); ReduceStamina(rollStamina, false); currentStaminaRecoveryDelay = 2f; } } public virtual void Dash() { if (isStrafing) { TriggerStrafeRoll(strafeRollForwardAnim, strafeRollBackwardAnim, strafeRollLeftAnim, strafeRollRightAnim); } else { OnRoll.Invoke(); isRolling = true; animator.CrossFadeInFixedTime("Dash", rollTransition, baseLayer); ReduceStamina(rollStamina, false); currentStaminaRecoveryDelay = 2f; } } // --- 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) { OnRoll.Invoke(); isRolling = true; ReduceStamina(rollStamina, false); currentStaminaRecoveryDelay = 2f; string animToPlay; float correctionAngle = 0f; // Determine the base "forward" direction from the camera, ignoring vertical tilt. 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) { // If no input, always perform a backward roll. animToPlay = backwardAnim; correctionAngle = strafeRollBackwardCorrectionAngle; } else { // 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); } // --- END OF FINAL METHOD --- 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); } } public override void UpdateAnimatorParameters() { if (disableAnimations) return; animator.SetBool(vAnimatorParameters.IsStrafing, isStrafing); animator.SetBool(vAnimatorParameters.IsSprinting, isSprinting); animator.SetBool(vAnimatorParameters.IsSliding, isSliding && !isRolling); animator.SetBool(vAnimatorParameters.IsCrouching, isCrouching); animator.SetBool(vAnimatorParameters.IsGrounded, isGrounded); animator.SetBool(vAnimatorParameters.IsDead, isDead); animator.SetFloat(vAnimatorParameters.GroundDistance, groundDistance); animator.SetFloat(vAnimatorParameters.GroundAngle, GroundAngleFromDirection()); if (!isGrounded) animator.SetFloat(vAnimatorParameters.VerticalVelocity, verticalVelocity); { if (isStrafing) { animator.SetFloat(vAnimatorParameters.InputHorizontal, horizontalSpeed, strafeSpeed.animationSmooth, Time.fixedDeltaTime); animator.SetFloat(vAnimatorParameters.InputVertical, verticalSpeed, strafeSpeed.animationSmooth, Time.fixedDeltaTime); } else { animator.SetFloat(vAnimatorParameters.InputVertical, verticalSpeed, freeSpeed.animationSmooth, Time.fixedDeltaTime); animator.SetFloat(vAnimatorParameters.InputHorizontal, useAnimationBasedRotation ? horizontalSpeed : 0, freeSpeed.animationSmooth, Time.fixedDeltaTime); } animator.SetFloat(vAnimatorParameters.InputMagnitude, Mathf.LerpUnclamped(inputMagnitude, 0f, stopMoveWeight), isStrafing ? strafeSpeed.animationSmooth : freeSpeed.animationSmooth, Time.fixedDeltaTime); if (useLeanMovementAnim && inputMagnitude >= 0.1f) { animator.SetFloat(vAnimatorParameters.RotationMagnitude, rotationMagnitude, leanSmooth, Time.fixedDeltaTime); } else if (useTurnOnSpotAnim && inputMagnitude < 0.1f) { animator.SetFloat(vAnimatorParameters.RotationMagnitude, (float)System.Math.Round(rotationMagnitude, 2), rotationMagnitude == 0 ? 0.1f : 0.01f, Time.fixedDeltaTime); } } } } }