// Copyright (C) 2023 Nicholas Maltbie
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using nickmaltbie.OpenKCC.Character.Config;
using nickmaltbie.OpenKCC.Input;
using nickmaltbie.OpenKCC.Utils;
using UnityEngine;
namespace nickmaltbie.OpenKCC.Character.Action
{
///
/// Jump action that can be performed by a character controller.
///
[Serializable]
public class JumpAction : ConditionalAction
{
[Header("Player Jump Settings")]
///
/// Action reference for jumping.
///
[Tooltip("Action reference for jumping.")]
[SerializeField]
public BufferedInput jumpInput;
///
/// Velocity of player jump in units per second.
///
[Tooltip("Vertical velocity of player jump.")]
[SerializeField]
public float jumpVelocity = 5.0f;
///
/// Maximum angle at which the player can jump (in degrees).
///
[Tooltip("Maximum angle at which the player can jump (in degrees).")]
[SerializeField]
[Range(0, 90)]
public float maxJumpAngle = 85f;
///
/// Weight to which the player's jump is weighted towards the direction
/// of the surface they are standing on.
///
[Tooltip("Weight to which the player's jump is weighted towards the angle of their surface.")]
[SerializeField]
[Range(0, 1)]
public float jumpAngleWeightFactor = 0.0f;
///
/// Grounded state for managing player grounded configuration.
///
private IKCCGrounded kccGrounded;
///
/// Actor to apply jumps to.
///
private IJumping actor;
///
/// Configuration of the character controller.
///
private IKCCConfig kccConfig;
///
/// Has the player jumped while they are sliding.
///
public bool JumpedWhileSliding { get; private set; }
///
/// Setup this jump action.
///
public void Setup(IKCCGrounded kccGrounded, IKCCConfig kccConfig, IJumping actor)
{
base.condition = CanJump;
this.kccGrounded = kccGrounded;
this.kccConfig = kccConfig;
this.actor = actor;
JumpedWhileSliding = false;
jumpInput.InputAction?.Enable();
}
///
public override void Update()
{
base.Update();
jumpInput?.Update();
if (kccGrounded != null && kccGrounded.StandingOnGround && !kccGrounded.Sliding)
{
JumpedWhileSliding = false;
}
}
///
/// Apply the jump action to the actor if attempting
/// jump and the player can jump.
///
/// True if the player jumped, false otherwise.
public bool ApplyJumpIfPossible(IKCCGrounded grounded)
{
kccGrounded = grounded;
if (AttemptingJump && CanPerform)
{
Jump();
return true;
}
return false;
}
///
/// Apply the jump to the player.
///
public void Jump()
{
if (kccGrounded.Sliding)
{
JumpedWhileSliding = true;
}
Vector3 jumpDirection = (kccGrounded.StandingOnGround ? kccGrounded.SurfaceNormal : kccConfig.Up) *
jumpAngleWeightFactor + kccConfig.Up * (1 - jumpAngleWeightFactor);
actor.ApplyJump(jumpVelocity * jumpDirection.normalized);
jumpInput.Reset();
}
///
/// Is the player currently attempting to jump
///
public bool AttemptingJump => jumpInput.Pressed;
///
/// Can the player jump based on their current state.
///
/// True if the player can jump, false otherwise.
public bool CanJump()
{
bool canJump = kccGrounded.StandingOnGround && kccGrounded.Angle <= maxJumpAngle;
if (canJump && !kccGrounded.Sliding)
{
return true;
}
else if (canJump)
{
return !JumpedWhileSliding;
}
return false;
}
}
}