using Invector; using Invector.vCharacterController; using Invector.vEventSystems; using Invector.vMelee; using UnityEngine; using System.Collections; using UnityEngine.Android; namespace Beyond { [vClassHeader("MELEE INPUT MANAGER", iconName = "inputIcon")] public class bMeleeCombatInput : bThirdPersonInput, vIMeleeFighter { // ... (rest of your existing bMeleeCombatInput variables) ... #region Variables [vEditorToolbar("Inputs")] [Header("Melee Inputs")] public GenericInput weakAttackInput = new GenericInput("Mouse0", "RB", "RB"); public GenericInput strongAttackInput = new GenericInput("Alpha1", false, "RT", true, "RT", false); public GenericInput blockInput = new GenericInput("R", "R", "R"); internal vMeleeManager meleeManager; protected bool _isAttacking; public bool BlockAttack { get; set; } public bool isAttacking { get => _isAttacking || (cc != null && cc.IsAnimatorTag("Attack")); protected set { _isAttacking = value; } } public bool isBlocking { get; protected set; } public bool isArmed { get { return meleeManager != null && (meleeManager.rightWeapon != null || (meleeManager.leftWeapon != null && meleeManager.leftWeapon.meleeType != vMeleeType.OnlyDefense)); } } public bool isEquipping { get; protected set; } [HideInInspector] public bool lockMeleeInput; [Header("Dash Settings")] [Tooltip("Speed at which the player rotates towards the target before dashing.")] public float dashRotationSpeed = 15f; [Tooltip("Angle threshold (degrees). If angle to target is less than this, player dashes immediately without pre-rotation.")] public float dashFacingAngleThreshold = 10f; [Tooltip("Maximum time (seconds) the pre-dash rotation will attempt before dashing anyway.")] public float maxDashRotationTime = 0.5f; private AutoTargetting autoTargeting; private bool isRotatingAndDashing = false; public void SetLockMeleeInput(bool value) { lockMeleeInput = value; if (value) { isAttacking = false; isBlocking = false; } } public override void SetLockAllInput(bool value) { base.SetLockAllInput(value); SetLockMeleeInput(value); } private MagicAttacks magicAttacks; #endregion public virtual bool lockInventory { get { return isAttacking || (cc != null && (cc.isDead || cc.customAction || cc.IsAnimatorTag("special") || cc.isRolling)); } } protected override void Start() { base.Start(); // This will call bThirdPersonInput's Start, which gets 'cc' magicAttacks = GetComponent(); autoTargeting = GetComponent(); if (autoTargeting == null && Player.Instance != null && Player.Instance.gameObject == this.gameObject) { autoTargeting = Player.Instance.GetComponent(); } if (autoTargeting == null && cc != null && cc.transform.root != null) { autoTargeting = cc.transform.root.GetComponentInChildren(true); } if (autoTargeting == null) { Debug.LogWarning("bMeleeCombatInput: AutoTargetting component not found. Dash towards target will require manual aiming."); } BlockAttack = false; } protected override void LateUpdate() { UpdateMeleeAnimations(); base.LateUpdate(); } protected override void FixedUpdate() { base.FixedUpdate(); ShowCursor(showCursorOnStart); } protected override void InputHandle() { if (cc == null || cc.isDead) return; base.InputHandle(); if (MeleeAttackConditions() && !lockMeleeInput) { MeleeWeakAttackInput(); MeleeStrongAttackInput(); BlockingInput(); } else { ResetAttackTriggers(); if (!blockInput.GetButton()) { isBlocking = false; } } } #region MeleeCombat Input Methods public virtual void MeleeWeakAttackInput() { if (cc.animator == null) return; if (weakAttackInput.GetButtonDown() && MeleeAttackStaminaConditions()) { TriggerWeakAttack(); } } public virtual void TriggerWeakAttack() { cc.animator.SetInteger(vAnimatorParameters.AttackID, AttackID); cc.animator.SetTrigger(vAnimatorParameters.WeakAttack); if (TimeController.Instance != null) { TimeController.Instance.Reset(); // Ensure normal time scale on attack start } } public virtual void MeleeStrongAttackInput() { if (cc.animator == null) return; if (strongAttackInput.GetButtonDown() && (!meleeManager.CurrentActiveAttackWeapon || meleeManager.CurrentActiveAttackWeapon.useStrongAttack) && MeleeAttackStaminaConditions()) { TriggerStrongAttack(); } } public virtual void TriggerStrongAttack() { cc.animator.SetInteger(vAnimatorParameters.AttackID, AttackID); cc.animator.SetTrigger(vAnimatorParameters.StrongAttack); } public virtual void BlockingInput() { if (cc.animator == null) return; isBlocking = blockInput.GetButton() && cc.currentStamina > 0 && !cc.customAction && !cc.IsAnimatorTag("special") && !isAttacking; } public override void ControlRotation() { if (cc == null || cc.lockRotation) return; if (cameraMain && !lockUpdateMoveDirection) { if (!cc.keepDirection) { cc.UpdateMoveDirection(cameraMain.transform); } } if (tpCamera != null && tpCamera.lockTarget && cc.isStrafing && !cc.isRolling) { cc.RotateToPosition(tpCamera.lockTarget.position); } else { cc.ControlRotationType(); } } protected override void SprintInput() { if (lockSprintInput) return; // Check local lock first if (sprintInput.useInput && cc != null) { bool canSprint = cc.useContinuousSprint ? sprintInput.GetButtonDown() : sprintInput.GetButton(); // Let base class (vThirdPersonInput) handle the cc.Sprint call if we don't override completely // For now, let's assume your bThirdPersonInput's SprintInput or vThirdPersonInput's one is what you want. // If it was base.SprintInput() before, and you have bThirdPersonInput overriding it, make sure that's intended. // For simplicity, directly call cc.Sprint if bThirdPersonInput doesn't add much, or call base.SprintInput() if it does. cc.Sprint(canSprint && !isAttacking); } } protected override void CrouchInput() { if (lockCrouchInput) return; base.CrouchInput(); // Call the base class logic from vThirdPersonInput/bThirdPersonInput } protected override void StrafeInput() { if (lockStrafeInput) return; base.StrafeInput(); // Call the base class logic from vThirdPersonInput/bThirdPersonInput } #endregion #region Conditions protected virtual bool MeleeAttackStaminaConditions() { if (meleeManager == null) meleeManager = GetComponent(); if (meleeManager == null || cc == null) return false; var result = cc.currentStamina - meleeManager.GetAttackStaminaCost(); return result >= 0; } public virtual bool MeleeAttackConditions() { if (meleeManager == null) meleeManager = GetComponent(); return !BlockAttack && meleeManager != null && cc != null && cc.isGrounded && !cc.customAction && !cc.IsAnimatorTag("special") && !cc.isJumping && !cc.isCrouching && !cc.isRolling && !isEquipping && (cc.animator != null && !cc.animator.IsInTransition(cc.baseLayer)) && !isRotatingAndDashing; } // We override JumpInput, so we'll use the base JumpConditions if needed, or reimplement. // vThirdPersonInput.JumpConditions(): // return !cc.customAction && !cc.isCrouching && cc.isGrounded && cc.GroundAngle() < cc.slopeLimit && cc.currentStamina >= cc.jumpStamina && !cc.isJumping && !cc.isRolling; protected override bool JumpConditions() // This is from vThirdPersonInput { if (cc == null) return false; bool baseConditions = !cc.customAction && !cc.isCrouching && cc.isGrounded && cc.GroundAngle() < cc.slopeLimit && cc.currentStamina >= cc.jumpStamina && !cc.isJumping && !cc.isRolling; return baseConditions && !isAttacking && !isRotatingAndDashing; // Add our specific conditions } // We override RollInput, so we'll use the base RollConditions or reimplement. // vThirdPersonInput.RollConditions(): // return (!cc.isRolling || cc.canRollAgain) && cc.isGrounded && !cc.customAction && cc.currentStamina > cc.rollStamina && !cc.isJumping && !cc.isSliding; protected override bool RollConditions() // This is from vThirdPersonInput { if (cc == null) return false; bool baseConditions = (!cc.isRolling || cc.canRollAgain) && cc.isGrounded && !cc.customAction && cc.currentStamina > cc.rollStamina && !cc.isJumping && !cc.isSliding; // Add any bMeleeCombatInput specific conditions if necessary, like !isAttacking return baseConditions && !isAttacking && !isRotatingAndDashing; } #endregion // MODIFIED JumpInput protected override void JumpInput() { if (lockJumpInput) return; // From bThirdPersonInput if (jumpInput.GetButtonDown() && JumpConditions()) // JumpConditions now includes !isAttacking & !isRotatingAndDashing { cc.Jump(true); } } // MODIFIED RollInput protected override void RollInput() { if (lockRollInput) return; // From bThirdPersonInput if (cc == null) return; if (isRotatingAndDashing) return; // Use the overridden RollConditions which now includes !isAttacking and !isRotatingAndDashing if (rollInput.GetButtonDown() && RollConditions()) { bThirdPersonController beyondController = cc as bThirdPersonController; if (beyondController == null) { Debug.LogError("cc is not a bThirdPersonController instance! Cannot Dash/Roll."); if(cc != null) cc.Roll(); // Fallback to cc's roll if cast fails return; } bool isInputBackwards = cc.input.z * -1f >= 0 && Mathf.Abs(cc.input.x) < 0.2f; // Or cc.rawInput.z if you use raw input for this decision if (!isInputBackwards) { beyondController.Roll(); // Or cc.Roll() if bThirdPersonController doesn't override it meaningfully } else // Forward, neutral, or sideways input { if (autoTargeting != null && autoTargeting.CurrentTarget != null) { Transform target = autoTargeting.CurrentTarget.transform; Vector3 directionToTarget = (target.position - cc.transform.position); directionToTarget.y = 0; if (directionToTarget.sqrMagnitude < 0.01f) // Very close { beyondController.Dash(); } else { float angleToTarget = Vector3.Angle(cc.transform.forward, directionToTarget.normalized); if (angleToTarget <= dashFacingAngleThreshold) { beyondController.Dash(); } else { if (!isRotatingAndDashing) // Should be redundant due to the check at the start of the method { StartCoroutine(RotateAndDashCoroutine(target, beyondController)); } } } } else { beyondController.Dash(); // No target, regular dash } } } } protected virtual IEnumerator RotateAndDashCoroutine(Transform target, bThirdPersonController controller) { if (isRotatingAndDashing || controller == null) yield break; isRotatingAndDashing = true; bool previousLockRotation = controller.lockRotation; controller.lockRotation = true; bool wasStrafing = controller.isStrafing; bool originalKeepDirection = controller.keepDirection; if (wasStrafing && controller.isStrafing) { controller.Strafe(); } controller.keepDirection = false; float startTime = Time.time; Vector3 directionToTarget; Quaternion targetRotationQuaternion; while (Time.time < startTime + maxDashRotationTime) { if (target == null || !target.gameObject.activeInHierarchy) break; if (controller == null) { isRotatingAndDashing = false; yield break; } directionToTarget = (target.position - controller.transform.position); directionToTarget.y = 0; if (directionToTarget.sqrMagnitude < 0.001f) break; targetRotationQuaternion = Quaternion.LookRotation(directionToTarget.normalized); controller.transform.rotation = Quaternion.Slerp(controller.transform.rotation, targetRotationQuaternion, Time.deltaTime * dashRotationSpeed); if (Vector3.Angle(controller.transform.forward, directionToTarget.normalized) <= dashFacingAngleThreshold) { controller.transform.rotation = targetRotationQuaternion; break; } yield return null; } if (controller == null) { isRotatingAndDashing = false; yield break; } if (target != null && target.gameObject.activeInHierarchy && (Time.time >= startTime + maxDashRotationTime || Vector3.Angle(controller.transform.forward, (target.position - controller.transform.position).normalized) > 0.1f) ) { directionToTarget = (target.position - controller.transform.position); directionToTarget.y = 0; if (directionToTarget.sqrMagnitude > 0.001f) { controller.transform.rotation = Quaternion.LookRotation(directionToTarget.normalized); } } controller.lockRotation = previousLockRotation; if (wasStrafing && !controller.isStrafing) { controller.Strafe(); } controller.keepDirection = originalKeepDirection; controller.Dash(); isRotatingAndDashing = false; } #region Melee Methods public virtual void OnEnableAttack() { if (meleeManager == null) meleeManager = GetComponent(); if (meleeManager == null || cc == null) return; cc.currentStaminaRecoveryDelay = meleeManager.GetAttackStaminaRecoveryDelay(); cc.currentStamina -= meleeManager.GetAttackStaminaCost(); isAttacking = true; if(cc!=null) cc.isSprinting = false; } public virtual void OnDisableAttack() { isAttacking = false; } public virtual void ResetAttackTriggers() { if (cc != null && cc.animator != null) { cc.animator.ResetTrigger(vAnimatorParameters.WeakAttack); cc.animator.ResetTrigger(vAnimatorParameters.StrongAttack); } } public virtual void BreakAttack(int breakAtkID) { ResetAttackTriggers(); OnRecoil(breakAtkID); } public virtual void OnRecoil(int recoilID) { if (cc != null && cc.animator != null) { cc.animator.SetInteger(vAnimatorParameters.RecoilID, recoilID); cc.animator.SetTrigger(vAnimatorParameters.TriggerRecoil); cc.animator.SetTrigger(vAnimatorParameters.ResetState); cc.animator.ResetTrigger(vAnimatorParameters.WeakAttack); cc.animator.ResetTrigger(vAnimatorParameters.StrongAttack); } } public virtual void OnReceiveAttack(vDamage damage, vIMeleeFighter attacker) { if (cc == null) return; if (magicAttacks != null && magicAttacks.IsShieldActive) return; if (!damage.ignoreDefense && isBlocking && meleeManager != null && meleeManager.CanBlockAttack(damage.sender.position)) { var damageReduction = meleeManager.GetDefenseRate(); if (damageReduction > 0) damage.ReduceDamage(damageReduction); if (attacker != null && meleeManager != null && meleeManager.CanBreakAttack()) { attacker.BreakAttack(meleeManager.GetDefenseRecoilID()); } meleeManager.OnDefense(); cc.currentStaminaRecoveryDelay = damage.staminaRecoveryDelay; cc.currentStamina -= damage.staminaBlockCost; } damage.hitReaction = !isBlocking || damage.ignoreDefense; cc.TakeDamage(damage); } public virtual vICharacter character { get { return cc; } } #endregion #region Update Animations public virtual int defaultMoveSetID { get; set; } public virtual bool overrideWeaponMoveSetID { get; set; } public virtual int meleeMoveSetID { get { if (meleeManager == null) meleeManager = GetComponent(); int id = meleeManager?.GetMoveSetID() ?? 0; if (id == 0 || overrideWeaponMoveSetID) id = defaultMoveSetID; return id; } } public virtual void ResetMeleeAnimations() { if (meleeManager == null || cc == null || cc.animator == null) return; cc.animator.SetBool(vAnimatorParameters.IsBlocking, false); } public virtual int AttackID { get { if (meleeManager == null) meleeManager = GetComponent(); return meleeManager?.GetAttackID() ?? 0; } } public virtual int DefenseID { get { if (meleeManager == null) meleeManager = GetComponent(); return meleeManager?.GetDefenseID() ?? 0; } } protected virtual void UpdateMeleeAnimations() { if (cc == null || cc.animator == null || meleeManager == null) return; cc.animator.SetInteger(vAnimatorParameters.AttackID, AttackID); cc.animator.SetInteger(vAnimatorParameters.DefenseID, DefenseID); cc.animator.SetBool(vAnimatorParameters.IsBlocking, isBlocking); cc.animator.SetFloat(vAnimatorParameters.MoveSet_ID, meleeMoveSetID, .2f, vTime.deltaTime); isEquipping = cc.IsAnimatorTag("IsEquipping"); } #endregion } }