// 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 nickmaltbie.OpenKCC.Character;
using nickmaltbie.OpenKCC.Character.Config;
using nickmaltbie.OpenKCC.Character.Events;
using nickmaltbie.OpenKCC.Environment.MovingGround;
using nickmaltbie.OpenKCC.Tests.TestCommon;
using nickmaltbie.OpenKCC.Utils.ColliderCast;
using nickmaltbie.TestUtilsUnity;
using nickmaltbie.TestUtilsUnity.Tests.TestCommon;
using NUnit.Framework;
using UnityEngine;
namespace nickmaltbie.OpenKCC.Tests.EditMode.Character
{
///
/// Basic tests for in edit mode.
///
[TestFixture]
public class KCCStateMachineTests : KCCStateMachineTestBase
{
private MockUnityService unityServiceMock;
private MockColliderCast colliderCastMock;
private MockCameraControls cameraControlsMock;
[SetUp]
public void SetUp()
{
base.Setup();
unityServiceMock = new MockUnityService();
colliderCastMock = kccStateMachine.gameObject.AddComponent();
cameraControlsMock = new MockCameraControls();
unityServiceMock.deltaTime = 1.0f;
unityServiceMock.fixedDeltaTime = 1.0f;
kccStateMachine.Awake();
kccStateMachine.CameraControls = cameraControlsMock;
moveEngine._colliderCast = colliderCastMock;
}
[TearDown]
public override void TearDown()
{
base.TearDown();
PlayerInputUtils.playerMovementState = PlayerInputState.Allow;
}
[Test]
public void Validate_KCCStateMachine_Falling_Transition()
{
KCCTestUtils.SetupCastSelf(colliderCastMock, didHit: false);
Assert.AreEqual(typeof(KCCStateMachine.IdleState), kccStateMachine.CurrentState);
unityServiceMock.deltaTime = 1.0f;
kccStateMachine.Update();
kccStateMachine.FixedUpdate();
kccStateMachine.FixedUpdate();
Assert.AreEqual(typeof(KCCStateMachine.FallingState), kccStateMachine.CurrentState);
Assert.IsFalse(KCCGroundedState.StandingOnGround);
Assert.IsFalse(KCCGroundedState.Sliding);
Assert.IsTrue(KCCGroundedState.Falling);
}
[Test]
public void Validate_KCCStateMachine_SprintingInput()
{
KCCTestUtils.SetupCastSelf(colliderCastMock, distance: 0.001f, normal: Vector3.up, didHit: true);
Set(moveStick, Vector2.up);
Set(sprintButton, 1);
kccStateMachine.Update();
kccStateMachine.FixedUpdate();
Assert.AreEqual(typeof(KCCStateMachine.SprintingState), kccStateMachine.CurrentState);
kccStateMachine.FixedUpdate();
}
[Test]
public void Validate_KCCStateMachine_Sliding_Transition()
{
KCCTestUtils.SetupCastSelf(colliderCastMock, distance: 0.001f, normal: Vector3.Lerp(Vector3.up, Vector3.forward, 0.9f), didHit: true);
Assert.AreEqual(kccStateMachine.CurrentState, typeof(KCCStateMachine.IdleState));
kccStateMachine.Update();
kccStateMachine.FixedUpdate();
Assert.AreEqual(kccStateMachine.CurrentState, typeof(KCCStateMachine.SlidingState));
Assert.IsTrue(KCCGroundedState.StandingOnGround);
Assert.IsTrue(KCCGroundedState.Sliding);
Assert.IsFalse(KCCGroundedState.Falling);
}
[Test]
public void Validate_KCCStateMachine_MovingGround_Teleport()
{
GameObject movingGround = CreateGameObject();
BoxCollider collider = movingGround.AddComponent();
KCCTestUtils.SetupCastSelf(colliderCastMock, distance: 0.001f, normal: Vector3.up, didHit: true, collider: collider);
kccStateMachine.Update();
kccStateMachine.FixedUpdate();
kccStateMachine.transform.position = Vector3.forward * 10;
Assert.AreEqual(kccStateMachine.transform.position, Vector3.forward * 10);
KCCTestUtils.SetupCastSelf(colliderCastMock, didHit: false);
kccStateMachine.Update();
kccStateMachine.FixedUpdate();
kccStateMachine.transform.position = Vector3.back * 10;
Assert.AreEqual(kccStateMachine.transform.position, Vector3.back * 10);
}
[Test]
public void Validate_KCCStateMachine_MovingGround_Update(
[ValueSource(nameof(TestDirections))] Vector3 direction,
[Values] bool shouldAttach
)
{
GameObject movingGround = CreateGameObject();
MovementTracking tracking = movingGround.AddComponent();
BoxCollider collider = movingGround.AddComponent();
KCCTestUtils.SetupCastSelf(colliderCastMock, distance: 0.001f, normal: Vector3.up, didHit: true, collider: collider);
var unityServiceMock = new MockUnityService();
tracking.shouldAttach = shouldAttach;
tracking.unityService = unityServiceMock;
unityServiceMock.fixedDeltaTime = 1.0f;
unityServiceMock.deltaTime = 1.0f;
tracking.FixedUpdate();
tracking.transform.position += direction;
tracking.FixedUpdate();
// Update the moving ground object and assert
// that the player is attached to the moving ground
kccStateMachine.Update();
kccStateMachine.FixedUpdate();
tracking.FixedUpdate();
tracking.transform.position += direction;
tracking.FixedUpdate();
Assert.AreEqual(movingGround, KCCGroundedState.Floor);
}
[Test]
public void Validate_KCCStateMachine_Move_Transition()
{
KCCTestUtils.SetupCastSelf(colliderCastMock, distance: 0.001f, normal: Vector3.up, didHit: true);
Set(moveStick, Vector2.up);
kccStateMachine.Update();
kccStateMachine.FixedUpdate();
Assert.AreEqual(typeof(KCCStateMachine.WalkingState), kccStateMachine.CurrentState);
TestUtils.AssertInBounds(kccStateMachine.InputMovement, Vector3.forward);
kccStateMachine.Update();
kccStateMachine.FixedUpdate();
Assert.AreEqual(typeof(KCCStateMachine.WalkingState), kccStateMachine.CurrentState);
Assert.IsTrue(KCCGroundedState.StandingOnGround);
Assert.IsFalse(KCCGroundedState.Sliding);
Assert.IsFalse(KCCGroundedState.Falling);
}
[Test]
public void Validate_KCCStateMachine_DontCancelVelocityWhenMovingUp()
{
KCCTestUtils.SetupCastSelf(colliderCastMock, distance: 0.001f, normal: Vector3.up, didHit: true);
kccStateMachine.Update();
// Set the character's velocity to some upward value
kccStateMachine.ApplyJump(Vector3.up * 5);
// Assert that the velocity is not zeroed out despite the player being
// grounded at the current time.
kccStateMachine.FixedUpdate();
Assert.IsTrue(KCCGroundedState.StandingOnGround);
Assert.AreNotEqual(Vector3.zero, kccStateMachine.Velocity);
}
[Test]
public void Validate_KCCStateMachine_ApplyJump([NUnit.Framework.Range(0.0f, 5.0f, 1.0f)] float strength)
{
Assert.AreEqual(typeof(KCCStateMachine.IdleState), kccStateMachine.CurrentState);
kccStateMachine.ApplyJump(Vector3.up * strength);
TestUtils.AssertInBounds(kccStateMachine.Velocity, Vector3.up * strength);
Assert.AreEqual(typeof(KCCStateMachine.JumpState), kccStateMachine.CurrentState);
}
[Test]
public void Validate_KCCStateMachine_PlayerInputUtilsFlags()
{
KCCTestUtils.SetupCastSelf(colliderCastMock, distance: 0.001f, normal: Vector3.up, didHit: true);
Set(moveStick, Vector2.up);
PlayerInputUtils.playerMovementState = PlayerInputState.Deny;
kccStateMachine.Update();
kccStateMachine.FixedUpdate();
TestUtils.AssertInBounds(kccStateMachine.InputMovement, Vector3.zero);
Assert.AreEqual(kccStateMachine.CurrentState, typeof(KCCStateMachine.IdleState));
kccStateMachine.Update();
kccStateMachine.FixedUpdate();
Assert.AreEqual(kccStateMachine.CurrentState, typeof(KCCStateMachine.IdleState));
Assert.IsTrue(KCCGroundedState.StandingOnGround);
Assert.IsFalse(KCCGroundedState.Sliding);
Assert.IsFalse(KCCGroundedState.Falling);
PlayerInputUtils.playerMovementState = PlayerInputState.Allow;
}
[Test]
public void Validate_KCCStateMachine_SprintSpeedValue()
{
kccStateMachine.RaiseEvent(StartMoveInput.Instance);
kccStateMachine.RaiseEvent(StartSprintEvent.Instance);
}
[Test]
public void Validate_KCCStateMachine_TeleportPlayer()
{
kccStateMachine.TeleportPlayer(Vector3.forward);
Assert.AreEqual(Vector3.forward, kccStateMachine.transform.position);
}
}
}