protected BufferZoneInfo GetBufferZoneInfo(float gravityScale, Direction.Vertical verticalDirection)
		{
			BufferZoneInfo bufferZoneInfo = new BufferZoneInfo();
			bool willSnapToLedgeHeight = wallClimb.enableClimbing || ledgeGrab.enableLedgeGrab;
			if(willSnapToLedgeHeight)
			{
				RaycastHelper.LedgeInfo ledgeInfo = RaycastHelper.DetectLedgeOnWall(controller.direction.horizontal, (Direction.Vertical)((int)verticalDirection * gravityScale), controller.slots.actor.GetComponent<BoxCollider2D>(), controller.slots.physicsObject.properties.velocity.y * PhysicsManager.Instance.fixedDeltaTime * -(int)verticalDirection);
				if(!ledgeInfo.didHit)
				{
					ledgeInfo = RaycastHelper.DetectLedgeOnWall(controller.direction.horizontal, (Direction.Vertical)((int)verticalDirection * gravityScale), controller.slots.actor.GetComponent<BoxCollider2D>(), controller.slots.physicsObject.properties.velocity.y * PhysicsManager.Instance.fixedDeltaTime * -(int)verticalDirection, 0.0f);
				}

				if(ledgeInfo.didHit)
				{
					bufferZoneInfo.isLedgeDetected = true;
					bufferZoneInfo.bufferCutoff = ledgeInfo.hitY - (controller.slots.actor.GetComponent<BoxCollider2D>().size.y * 0.5f * gravityScale * -(int)verticalDirection);
					float adjustedPositionY = transform.position.y + controller.slots.physicsObject.properties.velocity.y * PhysicsManager.Instance.fixedDeltaTime * -(int)verticalDirection;
					if(verticalDirection == Direction.Vertical.Down && ((gravityScale < 0.0f && adjustedPositionY < bufferZoneInfo.bufferCutoff) || (gravityScale > 0.0f && adjustedPositionY > bufferZoneInfo.bufferCutoff))) //At top of wall
					{
						bufferZoneInfo.isInBufferZone = true;
					}
					else if(verticalDirection == Direction.Vertical.Up && ((gravityScale < 0.0f && adjustedPositionY > bufferZoneInfo.bufferCutoff) || (gravityScale > 0.0f && adjustedPositionY < bufferZoneInfo.bufferCutoff)))
					{
						bufferZoneInfo.isInBufferZone = true;
					}
				}
			}

			return bufferZoneInfo;
		}
		void FixedUpdate()
		{
			if(!isEnabled || (controller.slots.actor.currentAttack != null && controller.slots.actor.currentAttack.cancels.wallClinging))
			{
				currentWallJumpGraceFrame = 0;
				currentCooldownFrame = 0;
				isClingingToWall = false;
				return;
			}

			if(controller.StateID() != FallingState.idString && !controller.isKnockbackActive && !controller.isStunned)
			{
				isDropping = false;
			}

			CheckForWallCling();

			bool isPressingIntoWall = IsPressingIntoWall();
			float gravityScale = controller.GravityScaleMultiplier();

			BufferZoneInfo bufferZoneInfo_Top = GetBufferZoneInfo(gravityScale, Direction.Vertical.Down);
			BufferZoneInfo bufferZoneInfo_Bottom = GetBufferZoneInfo(gravityScale, Direction.Vertical.Up);
			if(isPressingIntoWall && IsClingAllowed() && !isDropping)
			{
				if(!(IsJumpActive() && !enableClingWhileJumping))
				{
					if(bufferZoneInfo_Top.isLedgeDetected && !bufferZoneInfo_Top.isInBufferZone)
					{
						if((controller.slots.physicsObject.properties.velocity.y * gravityScale) < 0.0f && controller.StateID() != this.id)
						{
							controller.slots.actor.SetPosition(new Vector2(controller.slots.actor.transform.position.x, bufferZoneInfo_Top.bufferCutoff));

							if(ledgeGrab.enableLedgeGrab)
							{
								if(substate != Substate.LedgeHanging)
								{
									PlaySecondaryAnimation(animations.ledgeHang);
								}

                            	substate = Substate.LedgeHanging;
							}
						}
					}
					else if(bufferZoneInfo_Bottom.isLedgeDetected && !bufferZoneInfo_Bottom.isInBufferZone && wallClimb.enableClimbing)
					{
						if(!(bufferZoneInfo_Top.isLedgeDetected && bufferZoneInfo_Bottom.isLedgeDetected))
						{
							if((controller.slots.physicsObject.properties.velocity.y * gravityScale) > 0.0f && controller.StateID() != this.id)
							{
								controller.slots.actor.SetPosition(new Vector2(controller.slots.actor.transform.position.x, bufferZoneInfo_Bottom.bufferCutoff));
							}
						}
					}

				}
			}

			float climbableArea = GetComponent<BoxCollider2D>().size.y;
			if(bufferZoneInfo_Top.isLedgeDetected && bufferZoneInfo_Bottom.isLedgeDetected)
			{
				climbableArea = (bufferZoneInfo_Top.bufferCutoff - bufferZoneInfo_Bottom.bufferCutoff) * gravityScale;
			}

			if((bufferZoneInfo_Top.isLedgeDetected && bufferZoneInfo_Top.isInBufferZone && controller.slots.physicsObject.properties.velocity.y * gravityScale < 0.0f) || (bufferZoneInfo_Bottom.isLedgeDetected && bufferZoneInfo_Bottom.isInBufferZone && controller.slots.physicsObject.properties.velocity.y * gravityScale >= 0.0f))
			{
				bool isInBothBufferZones = false;
				if(bufferZoneInfo_Top.isLedgeDetected && bufferZoneInfo_Bottom.isLedgeDetected)
				{
					climbableArea = (bufferZoneInfo_Top.bufferCutoff - bufferZoneInfo_Bottom.bufferCutoff) * gravityScale;
					if(climbableArea < 0.0f && ((transform.position.y <= bufferZoneInfo_Top.bufferCutoff && gravityScale > 0.0f) || (transform.position.y >= bufferZoneInfo_Top.bufferCutoff && gravityScale < 0.0f)))
					{
						isInBothBufferZones = true;
					}
				}

				if(!isInBothBufferZones || !IsCooldownComplete() || isDropping) //Ordinarily, disengage from the wall; however, if we're in a buffer zone only because the entire wall is a buffer zone, don't disengage; allow us to stay at the top
				{
					isClingingToWall = false;
				}
			}

			if(wallSlideSpeed == 0.0f && !wallClimb.enableClimbing && substate != Substate.LedgeHanging)
			{
				isClingingToWall = false;
			}

			if(isClingingToWall)
			{
				currentWallJumpGraceFrame = wallJump.wallJumpGraceFrames;
				if(controller.StateID() != idString && !(IsJumpActive() && !enableClingWhileJumping))
				{
					if(controller.StateID() != LadderState.idString && IsCooldownComplete() && !bufferZoneInfo_Top.isInBufferZone)
					{
						if(!(bufferZoneInfo_Bottom.isLedgeDetected && bufferZoneInfo_Bottom.isInBufferZone && wallClimb.enableClimbing) || climbableArea < 0.0f)
						{
							Begin();
							controller.slots.physicsObject.SetVelocityX(0);
						}
					}
				}

				if(controller.currentState == this)
				{

					if(Mathf.Abs(wallSlideSpeed) > 0.0f)
					{
						controller.slots.physicsObject.FreezeGravityForSingleFrame();
						controller.slots.physicsObject.SetVelocityY(wallSlideSpeed * gravityScale * -1.0f);

						if(!wallClimb.enableClimbing && substate != Substate.LedgeHanging && !controller.slots.actor.IsAttacking())
						{
							if(substate != Substate.LedgeHanging)
							{
								PlayAnimation();
							}

							substate = Substate.Sliding;
						}
					}

					if(wallClimb.enableClimbing || substate == Substate.LedgeHanging)
					{
						controller.slots.physicsObject.FreezeGravityForSingleFrame();
						controller.slots.physicsObject.SetVelocityY(0.0f);
					}
				}

				if(wallClimb.enableClimbing && Mathf.Abs(controller.slots.input.verticalAxis) > 0.0f && controller.StateID() == idString)
				{
					float climbVelocity = controller.slots.input.verticalAxis * wallClimb.climbSpeed * gravityScale;
					if(climbableArea < GetComponent<BoxCollider2D>().size.y)
					{
						climbVelocity = 0.0f;
					}

					if(bufferZoneInfo_Top.isLedgeDetected)
					{
						if((gravityScale < 0.0f && transform.position.y + (climbVelocity * PhysicsManager.Instance.fixedDeltaTime) < bufferZoneInfo_Top.bufferCutoff) || (gravityScale > 0.0f && transform.position.y + (climbVelocity * PhysicsManager.Instance.fixedDeltaTime) > bufferZoneInfo_Top.bufferCutoff))
						{
							controller.slots.actor.SetPosition(new Vector2(controller.slots.actor.transform.position.x, bufferZoneInfo_Top.bufferCutoff));
							climbVelocity = 0.0f;

							if(ledgeGrab.enableLedgeGrab)
							{
								if(substate != Substate.LedgeHanging && !controller.slots.actor.IsAttacking())
								{
									PlaySecondaryAnimation(animations.ledgeHang);
								}

								substate = Substate.LedgeHanging;
							}
						}
					}
					else if(bufferZoneInfo_Bottom.isLedgeDetected)
					{
						if((gravityScale < 0.0f && transform.position.y + (climbVelocity * PhysicsManager.Instance.fixedDeltaTime) > bufferZoneInfo_Bottom.bufferCutoff) || (gravityScale > 0.0f && transform.position.y + (climbVelocity * PhysicsManager.Instance.fixedDeltaTime) < bufferZoneInfo_Bottom.bufferCutoff))
						{
							controller.slots.actor.SetPosition(new Vector2(controller.slots.actor.transform.position.x, bufferZoneInfo_Bottom.bufferCutoff));
							climbVelocity = 0.0f;
						}
					}

					if(controller.currentState == this)
					{
						controller.slots.physicsObject.SetVelocityY(climbVelocity);
					}

					if(!(substate == Substate.LedgeHanging && controller.slots.input.verticalAxis > 0.0f))
					{
						if(substate != Substate.Climbing && !controller.slots.actor.IsAttacking())
						{
							PlaySecondaryAnimation(animations.climbWallMoving);
						}

						substate = Substate.Climbing;
					}
				}
				else if(wallClimb.enableClimbing)
				{
					if(!bufferZoneInfo_Top.isInBufferZone && substate != Substate.LedgeHanging && substate != Substate.Sliding)
					{
						if(substate != Substate.Stopped && !controller.slots.actor.IsAttacking())
						{
							PlaySecondaryAnimation(animations.climbWallStopped);
						}

						substate = Substate.Stopped;
					}
				}
			}
			else
			{
				if(controller.StateID() == this.id)
				{
					controller.SetStateToDefault();
				}
			}

			UpdateCooldownFrames();
		}