/// <summary> /// Perform climbing check, and if successful, start climbing movement. /// </summary> public void ClimbingCheck() { bool advancedClimbingOn = DaggerfallUnity.Settings.AdvancedClimbing; // true if we should try climbing wall and are airborne bool airborneGraspWall = (!isClimbing && !isSlipping && acrobatMotor.Falling); bool inputBack = InputManager.Instance.HasAction(InputManager.Actions.MoveBackwards); bool inputForward = InputManager.Instance.HasAction(InputManager.Actions.MoveForwards); //inputForward = true; // boolean that means ground directly below us is too close for climbing or rappelling bool tooCloseToGroundForClimb = (((isClimbing && (inputBack || isSlipping)) || airborneGraspWall) // short circuit evaluate the raycast, also prevents bug where you could teleport across town && Physics.Raycast(controller.transform.position, Vector3.down, controller.height / 2 + 0.12f)); CalcFrequencyAndToleranceOfWallChecks(airborneGraspWall); bool inputAbortCondition; if (advancedClimbingOn) { // TODO: prevent crouch from toggling crouch when aborting climb inputAbortCondition = (InputManager.Instance.HasAction(InputManager.Actions.Crouch) || InputManager.Instance.HasAction(InputManager.Actions.Jump)); } else { inputAbortCondition = !inputForward; } // reset for next use WallEject = false; if (releasedFromCeiling) { MoveForwardIfNotClimbing(); } if ((playerMotor.CollisionFlags & CollisionFlags.Sides) != 0) { releasedFromCeiling = false; } bool horizontallyStationary = Vector2.Distance(lastHorizontalPosition, new Vector2(controller.transform.position.x, controller.transform.position.z)) < startClimbHorizontalTolerance; bool touchingSides = (playerMotor.CollisionFlags & CollisionFlags.Sides) != 0; bool touchingGround = (playerMotor.CollisionFlags & CollisionFlags.Below) != 0; //bool touchingAbove = (playerMotor.CollisionFlags & CollisionFlags.Above) != 0; bool slippedToGround = isSlipping && touchingGround; bool nonOrthogonalStart = !isClimbing && inputForward && !horizontallyStationary; //bool forwardStationaryNearCeiling = inputForward && hangingMotor.IsWithinHangingDistance && horizontallyStationary; bool pushingFaceAgainstWallNearCeiling = false; //hangingMotor.IsHanging && !isClimbing && touchingSides && forwardStationaryNearCeiling; bool climbingOrForwardOrGrasping = (isClimbing || inputForward || airborneGraspWall); bool hangTouchNonVertical = false; //hangingMotor.IsHanging && touchingSides && Physics.Raycast(controller.transform.position, controller.transform.forward, out hit, 0.40f) && Mathf.Abs(hit.normal.y) > 0.06f; ClimbQuitMoveUnderToHang = (inputBack && !moveScanner.HitSomethingInFront && moveScanner.FrontUnderCeiling != null); // Allow climbing slight overhangs when capsule hits above but upwards test rays have clear space overhead // Works by gently bumping player capsule away from wall at point of contact so they can acquire new vertical climb position // Provided angle not too extreme then player will keep climbing upwards or downwards // Allows player to climb up gently angled positions like the coffin tunnel in Scourg Barrow and up over shallow eaves if (isClimbing && (playerMotor.CollisionFlags & CollisionFlags.Above) == CollisionFlags.Above) { Vector3 frontTestPosition = controller.transform.position + wallDirection * controller.radius * 0.9f; Vector3 backTestPosition = controller.transform.position - wallDirection * controller.radius * 0.1f; //Debug.DrawLine(frontTestPosition, frontTestPosition + Vector3.up * 2, Color.red); //Debug.DrawLine(backTestPosition, backTestPosition + Vector3.up * 2, Color.red); // Test close to front of head for backward sloping wall like Scourg Barrow and bump a little backwards // Then slightly further back for short overhangs like eaves and bump more backwards and a little upwards // Height of raycast test is extended to help ensure there is clear space above not just an angled ceiling if (!Physics.Raycast(frontTestPosition, Vector3.up, controller.height / 2 + 0.3f)) { controller.transform.position += -wallDirection * 0.1f; } else if (!Physics.Raycast(backTestPosition, Vector3.up, controller.height / 2 + 0.5f)) { controller.transform.position += -wallDirection * 0.4f + Vector3.up * 0.3f; } } // Handle recently restoring from save game where climbing active if (isClimbing && touchingSidesRestoreForce && !touchingSides) { touchingSides = true; //Debug.Log("Forced touchingSides..."); } else { touchingSidesRestoreForce = false; } // Should we reset climbing starter timers? wasClimbing = isClimbing; if ((!pushingFaceAgainstWallNearCeiling) && (inputAbortCondition || ClimbQuitMoveUnderToHang || !climbingOrForwardOrGrasping || !touchingSides && !releasedFromCeiling || levitateMotor.IsLevitating || playerMotor.IsRiding || slippedToGround // quit climbing if climbing down and ground is really close, prevents teleportation bug || tooCloseToGroundForClimb // don't do horizontal position check if already climbing || nonOrthogonalStart // if we're hanging, and touching sides with a wall that isn't mostly vertical || hangTouchNonVertical)) { if (isClimbing && inputAbortCondition && advancedClimbingOn) { WallEject = true; } StopClimbing(); releasedFromCeiling = false; // Reset position for horizontal distance check and timer to wait for climbing start lastHorizontalPosition = new Vector2(controller.transform.position.x, controller.transform.position.z); } else // countdown climbing events { // countdown to climbing start if (climbingStartTimer <= (playerMotor.systemTimerUpdatesDivisor * startClimbSkillCheckFrequency)) { climbingStartTimer += Time.deltaTime; } // Begin Climbing else if (!isClimbing) { //if (hangingMotor.IsHanging) //{ // grab wall from ceiling // overrideSkillCheck = true; // releasedFromCeiling = true; //} // automatic success if not falling if ((!airborneGraspWall /*&& !hangingMotor.IsHanging*/) || releasedFromCeiling) { if (ClimbingSkillCheck(startClimbMinChance)) { StartClimbing(); } } // skill check to see if we catch the wall else if (ClimbingSkillCheck(graspWallMinChance)) { StartClimbing(); } else { climbingStartTimer = 0; } } if (isClimbing) { // countdown to climb update, Faster updates if slipping if (climbingContinueTimer <= (playerMotor.systemTimerUpdatesDivisor * (isSlipping ? regainHoldSkillCheckFrequency : continueClimbingSkillCheckFrequency))) { climbingContinueTimer += Time.deltaTime; } else { climbingContinueTimer = 0; // don't allow slipping if not moving. if (!InputManager.Instance.HasAction(InputManager.Actions.MoveForwards) && !InputManager.Instance.HasAction(InputManager.Actions.MoveBackwards) && !InputManager.Instance.HasAction(InputManager.Actions.MoveLeft) && !InputManager.Instance.HasAction(InputManager.Actions.MoveRight)) { isSlipping = false; } // it's harder to regain hold while slipping than it is to continue climbing with a good hold on wall else if (isSlipping) { isSlipping = !ClimbingSkillCheck(regainHoldMinChance); } else { isSlipping = !ClimbingSkillCheck(continueClimbMinChance); } } } } // Climbing Cycle if (isClimbing) { // evalate the ledge direction GetClimbedWallInfo(); ClimbMovement(); // both variables represent similar situations, but different context acrobatMotor.Falling = isSlipping; } else if (!rappelMotor.IsRappelling) { isSlipping = false; atOutsideCorner = false; atInsideCorner = false; showClimbingModeMessage = true; moveDirection = Vector3.zero; // deletes locally and saves myLedgeDirection to variable in class if (myLedgeDirection != Vector3.zero) { moveScanner.CutAndPasteVectorToDetached(ref myLedgeDirection); } } }
/// <summary> /// Perform climbing check, and if successful, start climbing movement. /// </summary> public void ClimbingCheck() { bool advancedClimbingOn = DaggerfallUnity.Settings.AdvancedClimbing; // true if we should try climbing wall and are airborne bool airborneGraspWall = (!isClimbing && !isSlipping && acrobatMotor.Falling); bool inputBack = InputManager.Instance.HasAction(InputManager.Actions.MoveBackwards); bool inputForward = InputManager.Instance.HasAction(InputManager.Actions.MoveForwards); //inputForward = true; // boolean that means ground directly below us is too close for climbing or rappelling bool tooCloseToGroundForClimb = (((isClimbing && (inputBack || isSlipping)) || airborneGraspWall) // short circuit evaluate the raycast, also prevents bug where you could teleport across town && Physics.Raycast(controller.transform.position, Vector3.down, controller.height / 2 + 0.12f)); CalcFrequencyAndToleranceOfWallChecks(airborneGraspWall); bool inputAbortCondition; if (advancedClimbingOn) { // TODO: prevent crouch from toggling crouch when aborting climb inputAbortCondition = (InputManager.Instance.HasAction(InputManager.Actions.Crouch) || InputManager.Instance.HasAction(InputManager.Actions.Jump)); } else { inputAbortCondition = !inputForward; } // reset for next use WallEject = false; if (releasedFromCeiling) { MoveForwardIfNotClimbing(); } if ((playerMotor.CollisionFlags & CollisionFlags.Sides) != 0) { releasedFromCeiling = false; } bool horizontallyStationary = Vector2.Distance(lastHorizontalPosition, new Vector2(controller.transform.position.x, controller.transform.position.z)) < startClimbHorizontalTolerance; bool touchingSides = (playerMotor.CollisionFlags & CollisionFlags.Sides) != 0; bool touchingGround = (playerMotor.CollisionFlags & CollisionFlags.Below) != 0; //bool touchingAbove = (playerMotor.CollisionFlags & CollisionFlags.Above) != 0; bool slippedToGround = isSlipping && touchingGround; bool nonOrthogonalStart = !isClimbing && inputForward && !horizontallyStationary; //bool forwardStationaryNearCeiling = inputForward && hangingMotor.IsWithinHangingDistance && horizontallyStationary; bool pushingFaceAgainstWallNearCeiling = false; //hangingMotor.IsHanging && !isClimbing && touchingSides && forwardStationaryNearCeiling; bool climbingOrForwardOrGrasping = (isClimbing || inputForward || airborneGraspWall); bool hangTouchNonVertical = false; //hangingMotor.IsHanging && touchingSides && Physics.Raycast(controller.transform.position, controller.transform.forward, out hit, 0.40f) && Mathf.Abs(hit.normal.y) > 0.06f; ClimbQuitMoveUnderToHang = (inputBack && !moveScanner.HitSomethingInFront && moveScanner.FrontUnderCeiling != null); // Should we reset climbing starter timers? if ((!pushingFaceAgainstWallNearCeiling) && (inputAbortCondition || ClimbQuitMoveUnderToHang || !climbingOrForwardOrGrasping || !touchingSides && !releasedFromCeiling || levitateMotor.IsLevitating || playerMotor.IsRiding || slippedToGround // quit climbing if climbing down and ground is really close, prevents teleportation bug || tooCloseToGroundForClimb // don't do horizontal position check if already climbing || nonOrthogonalStart // if we're hanging, and touching sides with a wall that isn't mostly vertical || hangTouchNonVertical)) { if (isClimbing && inputAbortCondition && advancedClimbingOn) { WallEject = true; } StopClimbing(); releasedFromCeiling = false; // Reset position for horizontal distance check and timer to wait for climbing start lastHorizontalPosition = new Vector2(controller.transform.position.x, controller.transform.position.z); } else // countdown climbing events { // countdown to climbing start if (climbingStartTimer <= (playerMotor.systemTimerUpdatesDivisor * startClimbSkillCheckFrequency)) { climbingStartTimer += Time.deltaTime; } // Begin Climbing else if (!isClimbing) { //if (hangingMotor.IsHanging) //{ // grab wall from ceiling // overrideSkillCheck = true; // releasedFromCeiling = true; //} // automatic success if not falling if ((!airborneGraspWall /*&& !hangingMotor.IsHanging*/) || releasedFromCeiling) { if (ClimbingSkillCheck(startClimbMinChance)) { StartClimbing(); } } // skill check to see if we catch the wall else if (ClimbingSkillCheck(graspWallMinChance)) { StartClimbing(); } else { climbingStartTimer = 0; } } if (isClimbing) { // countdown to climb update, Faster updates if slipping if (climbingContinueTimer <= (playerMotor.systemTimerUpdatesDivisor * (isSlipping ? regainHoldSkillCheckFrequency : continueClimbingSkillCheckFrequency))) { climbingContinueTimer += Time.deltaTime; } else { climbingContinueTimer = 0; // don't allow slipping if not moving. if (!InputManager.Instance.HasAction(InputManager.Actions.MoveForwards) && !InputManager.Instance.HasAction(InputManager.Actions.MoveBackwards) && !InputManager.Instance.HasAction(InputManager.Actions.MoveLeft) && !InputManager.Instance.HasAction(InputManager.Actions.MoveRight)) { isSlipping = false; } // it's harder to regain hold while slipping than it is to continue climbing with a good hold on wall else if (isSlipping) { isSlipping = !ClimbingSkillCheck(regainHoldMinChance); } else { isSlipping = !ClimbingSkillCheck(continueClimbMinChance); } } } } // Climbing Cycle if (isClimbing) { // evalate the ledge direction GetClimbedWallInfo(); ClimbMovement(); // both variables represent similar situations, but different context acrobatMotor.Falling = isSlipping; } else if (!rappelMotor.IsRappelling) { isSlipping = false; atOutsideCorner = false; atInsideCorner = false; showClimbingModeMessage = true; moveDirection = Vector3.zero; // deletes locally and saves myLedgeDirection to variable in class if (myLedgeDirection != Vector3.zero) { moveScanner.CutAndPasteVectorToDetached(ref myLedgeDirection); } } }