using System; using BITFALL.Entities.Player.Movement.States; using BITFALL.Player.Equip; using BITFALL.Player.Movement; using BITKit; using BITKit.Entities; using BITKit.PlayerCamera; using BITKit.StateMachine; using Cysharp.Threading.Tasks; using Lightbug.CharacterControllerPro.Core; using UnityEngine; using UnityEngine.InputSystem; // ReSharper disable InvertIf // ReSharper disable RedundantJumpStatement // ReSharper disable ConvertToConstant.Global namespace BITFALL.Entities.Player.Movement.States { public abstract class BasicMovement : PlayerCharacterState, IPlayerMovementState { [SerializeField] protected Vector3 initialCameraPosition; [SerializeField] protected float initialSpeed = 3f; [SerializeField] protected float initialJumpForce = 5f; [Inject] protected IKnockdown knockdown; [Inject] protected IPlayerCameraService _cameraService; public override void BeforeUpdateMovement(float deltaTime) { characterController.CurrentCameraPosition.shouldBe = initialCameraPosition; } public override void AfterUpdateMovement(float deltaTime) { if (knockdown.IsKnockdown) { characterController.TransitionState(); } if (knockdown.IsKnockdown is false && characterController.ExpectParachute.shouldBe) { characterController.TransitionState(); characterController.ExpectParachute.Reset(); } else if (characterController.topBlocked is false && characterController.RequestClimb && characterController.climbClosePoint.TryGetClosePoint(out var closePoint)) { characterController.ExpectClimb.shouldBe = closePoint; characterController.TransitionState(); return; } } public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime) { var rotation = Quaternion.Euler(0, characterController.LookInput.y, 0); var movementInput = characterController.MovementInput; if (_cameraService?.IsCameraActivated is false) { rotation =Quaternion.LookRotation( _cameraService.CameraRotation * Vector3.forward,Vector3.up); } var moveVelocity = rotation * new Vector3( movementInput.x * Mathf.Min(characterController.initialSpeed, initialSpeed), 0, movementInput.z * initialSpeed ); if (_cameraService?.IsCameraActivated is false) { moveVelocity = rotation * movementInput * initialSpeed; } if (characterController.limitSpeed.Allow) { switch (characterController.CurrentState) { case IPlayerWalkState: case IPlayerCrouchState: moveVelocity = Vector3.ClampMagnitude(moveVelocity, characterController.limitSpeed.Value); break; } } if (characterController.IsGrounded) { var effectiveGroundNormal = actor.GroundStableNormal; var inputRight = Vector3.Cross(moveVelocity, Vector3.up); var reorientedInput = Vector3.Cross(effectiveGroundNormal, inputRight).normalized * moveVelocity.magnitude; var targetMovementVelocity = reorientedInput; //* initialSpeed; // Smooth movement Velocity var newVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1f - Mathf.Exp(-16 * deltaTime)); currentVelocity = newVelocity; if (characterController.ExpectJump.shouldBe) { actor.ForceNotGrounded(); currentVelocity += Vector3.up * initialJumpForce; characterController.ExpectJump.Reset(); characterController.ExecuteCommand(); } } else { if (moveVelocity.sqrMagnitude > 0f) { var addedVelocity = moveVelocity * (3 * deltaTime); var currentVelocityOnInputsPlane = Vector3.ProjectOnPlane(currentVelocity, Vector3.up); // Limit air velocity from inputs if (currentVelocityOnInputsPlane.magnitude < 5) { // clamp addedVel to make total vel not exceed max vel on inputs plane Vector3 newTotal = Vector3.ClampMagnitude(currentVelocityOnInputsPlane + addedVelocity, 5); addedVelocity = newTotal - currentVelocityOnInputsPlane; } else { // Make sure added vel doesn't go in the direction of the already-exceeding velocity if (Vector3.Dot(currentVelocityOnInputsPlane, addedVelocity) > 0f) { addedVelocity = Vector3.ProjectOnPlane(addedVelocity, currentVelocityOnInputsPlane.normalized); } } // Prevent air-climbing sloped walls if (actor.WallCollision) { if (Vector3.Dot(currentVelocity + addedVelocity, addedVelocity) > 0f) { var perpenticularObstructionNormal = Vector3 .Cross(Vector3.Cross(Vector3.up, actor.GroundContactNormal), Vector3.up).normalized; addedVelocity = Vector3.ProjectOnPlane(addedVelocity, perpenticularObstructionNormal); } } // Apply added velocity currentVelocity += addedVelocity; } // Gravity currentVelocity += -Vector3.up * (30 * deltaTime); // Drag currentVelocity *= (1f / (1f + (0.1f * deltaTime))); } } public override void UpdateRotation(ref Quaternion currentRotation, float deltaTime) { var newRotation = Quaternion.Euler(0, characterController.LookInput.y, 0); if (_cameraService.IsCameraActivated is false) { if (Physics.Raycast(_cameraService.CameraPosition, _cameraService.CameraRotation * Vector3.forward, out var hit, 256, LayerMask.GetMask("Default"), QueryTriggerInteraction.Ignore)) { characterController.ViewForward = (hit.point - (characterController.Position + characterController.Rotation * characterController.ViewCenter)).normalized; newRotation = Quaternion.LookRotation(characterController.ViewForward); characterController.ViewRotation = newRotation; characterController.FocusPoint = hit.point; Debug.DrawLine(_cameraService.CameraPosition, hit.point, Color.green, 0.1f); Debug.DrawLine(characterController.Position + characterController.ViewCenter, characterController.Position + characterController.ViewCenter + characterController.ViewRotation * Vector3.forward , Color.blue, 0.1f); } else { characterController.ViewRotation = Quaternion.Euler(characterController.LookInput);; characterController.FocusPoint = characterController.Position + characterController.ViewRotation * Vector3.forward * 256; Debug.DrawLine(_cameraService.CameraPosition, _cameraService.CameraRotation * Vector3.forward, Color.red, 0.1f); } var rotationDirection = _cameraService.CameraRotation * characterController.MovementInput; rotationDirection = Vector3.ProjectOnPlane(rotationDirection, Vector3.up); if (rotationDirection.sqrMagnitude >= 0.16f) { var newPlayerRotation =Quaternion.LookRotation(rotationDirection) ; currentRotation = Quaternion.Lerp(currentRotation, newPlayerRotation, 1f - Mathf.Exp(-16 * deltaTime)); } if (characterController.allowFocus) { currentRotation = Quaternion.LookRotation(_cameraService.CameraRotation * Vector3.forward, Vector3.up); } } else { currentRotation = newRotation; } } } [Serializable] public sealed class Walk:BasicMovement,IPlayerWalkState { public override void AfterUpdateMovement(float deltaTime) { base.AfterUpdateMovement(deltaTime); if (characterController.CurrentState != this) return; switch (characterController.ExpectRun.shouldBe,characterController.ExpectCrouch.shouldBe) { case (_,true): characterController.TransitionState(); return; case (true,false) when characterController.pauseRun.Allow is false: characterController.TransitionState(); return; } } } [Serializable] public sealed class Parachute : PlayerCharacterState, IPlayerParachuteState { [SerializeField] private float moveDamping = 3f; [SerializeField] private float damping = 1f; [Inject] private IEquipService _equipService; public override void OnStateEntry(IState old) { base.OnStateEntry(old); characterController.InvokeOpenParachute(); _equipService.AllowEquip.AddDisableElements(this); } public override void OnStateExit(IState old, IState newState) { base.OnStateExit(old, newState); characterController.InvokeCloseParachute(); _equipService.AllowEquip.RemoveDisableElements(this); } public override void UpdateRotation(ref Quaternion currentRotation, float deltaTime) { currentRotation = Quaternion.Euler(0,characterController.LookInput.y,0); } public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime) { var rotation = Quaternion.Euler(0,characterController.LookInput.y,0); var moveVelocity = rotation * characterController.MovementInput; currentVelocity.y = Mathf.MoveTowards( currentVelocity.y, -8, damping * deltaTime ); currentVelocity+= moveVelocity.normalized * (moveDamping * deltaTime); currentVelocity.x = Mathf.Clamp(currentVelocity.x, -moveDamping, moveDamping); currentVelocity.z = Mathf.Clamp(currentVelocity.z, -moveDamping, moveDamping); } public override void AfterUpdateMovement(float deltaTime) { if (actor.IsGrounded) { characterController.TransitionState(); } characterController.ExpectParachute.Reset(); characterController.CurrentCameraPosition.shouldBe = characterController.FpvLocalPosition; } } [Serializable] public sealed class Run:BasicMovement,IPlayerRunState { public override void OnStateEntry(IState old) { characterController.ExpectRun.being = true; } public override void OnStateExit(IState old, IState newState) { characterController.ExpectRun.being = false; } public override void AfterUpdateMovement(float deltaTime) { base.AfterUpdateMovement(deltaTime); switch (characterController.ExpectRun.shouldBe,characterController.ExpectCrouch.shouldBe) { case (_,true): characterController.TransitionState(); return; case (false,false): characterController.TransitionState(); return; } if (characterController.pauseRun.Allow) { characterController.TransitionState(); return; } if (characterController.ExpectSprint.shouldBe) { characterController.TransitionState(); return; } } public override void ExecuteCommand(T command) { if (Enabled is false) return; if (command is PlayerCancelRunCommand) { characterController.TransitionState(); } } } [Serializable] public sealed class Slide : BasicMovement, IPlayerSlideState { [Header(nameof(Slide))] [SerializeField] private float additiveSpeed = 1f; [SerializeField] private float damping = 1; [SerializeField] private float stopSpeed = 0.64f; [SerializeField] private bool alwaysSlide; private bool _addedVelocity; public override void OnStateEntry(IState old) { characterController.ExpectCrouch.being = true; characterController.ExpectCrouch.shouldBe = true; characterController.ExecuteCommand(); _addedVelocity = false; } public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime) { if (alwaysSlide && actor.IsGrounded is false) { base.UpdateVelocity(ref currentVelocity, deltaTime); } else { if (_addedVelocity is false) { currentVelocity += currentVelocity.normalized * additiveSpeed; _addedVelocity = true; } else { currentVelocity = Vector3.Lerp(currentVelocity, Vector3.zero, deltaTime * damping); } } } public override void AfterUpdateMovement(float deltaTime) { if (characterController.ExpectRun.shouldBe) { characterController.TransitionState(); return; } if (characterController.ExpectCrouch.shouldBe is false || (alwaysSlide is false && actor.IsGrounded is false)) { characterController.TransitionState(); return; } if (actor.Velocity.sqrMagnitude <= stopSpeed) { characterController.TransitionState(); } characterController.CurrentCameraPosition.shouldBe = characterController.FpvLocalPosition; } } [Serializable] public sealed class Crouch:BasicMovement,IPlayerCrouchState { public override void OnStateEntry(IState old) { characterController.ExpectCrouch.being = true; } public override void OnStateExit(IState old, IState newState) { characterController.ExpectCrouch.being = false; } public override void AfterUpdateMovement(float deltaTime) { base.AfterUpdateMovement(deltaTime); if (characterController.ExpectRun.shouldBe) { characterController.TransitionState(); return; } if (characterController.ExpectCrouch.shouldBe is false) { characterController.TransitionState(); return; } } } [Serializable] public sealed class Sprint:BasicMovement,IPlayerSprintState { [SerializeField] private int slideCost = 16; [SerializeField] private int staminaCost = 1; public override void OnStateEntry(IState old) { characterController.ExpectSprint.being = true; } public override void OnStateExit(IState old, IState newState) { characterController.ExpectSprint.being = false; } public override void OnStateUpdate(float deltaTime) { characterController.Stamina -= staminaCost * deltaTime; } public override void AfterUpdateMovement(float deltaTime) { base.AfterUpdateMovement(deltaTime); if (characterController.pauseRun.Allow) { characterController.TransitionState(); return; } switch (characterController.ExpectRun.shouldBe,characterController.ExpectCrouch.shouldBe) { case (_,_) when characterController.Stamina is 0: characterController.ExpectSprint.Reset(); characterController.TransitionState(); return; case (_,true) when characterController.IsGrounded && characterController.Stamina > 0: characterController.Stamina -= slideCost; characterController.TransitionState(); return; case (_,true): characterController.TransitionState(); return; case (false,false): characterController.TransitionState(); return; } } public override void ExecuteCommand(T command) { if (Enabled is false) return; if(command is PlayerCancelRunCommand) characterController.TransitionState(); } } [Serializable] public sealed class Knockdown : BasicMovement, IPlayerKnockdownState { public override void Initialize() { base.Initialize(); knockdown.OnKnockdown += OnKnockdown; knockdown.OnRevive += OnRevive; } public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime) { base.UpdateVelocity(ref currentVelocity, deltaTime); if (knockdown.IsPressured) { currentVelocity.x = 0; currentVelocity.z = 0; } } private void OnRevive() { if(Enabled)characterController.TransitionState(); } private void OnKnockdown() { characterController.TransitionState(); } } [Serializable] public sealed class Clip:PlayerCharacterState { [BITCommand] public static void NoClip() { _clipAction?.Invoke(); } private static Action _clipAction; [SerializeField] private InputActionReference clipAction; [SerializeReference, SubclassSelector] private IReference clipEnv; [Inject] private IHealth _health; [Inject] private InputActionGroup _inputActionGroup; public override void Initialize() { base.Initialize(); _health.OnSetAlive += OnSetAlive; Data.AddListener(clipEnv.Value, OnClip); _clipAction = _ClipAction; characterController.destroyCancellationToken.Register(() => { Data.RemoveListender(clipEnv.Value, OnClip); }); } private void _ClipAction() { var clip = Data.Get(clipEnv.Value); Data.Set(clipEnv.Value, !clip); } private async void OnClip(bool obj) { await UniTask.SwitchToMainThread(); if(_health.IsAlive is false) return; if (obj && Enabled is false) { characterController.TransitionState(); //BIT4Log.Log("NoClip Enabled"); }else if (Enabled && obj is false) { characterController.TransitionState(); //BIT4Log.Log("NoClip Disabled"); } } private void OnSetAlive(bool obj) { if (Enabled && obj is false) { characterController.TransitionState(); } } public override void OnStateEntry(IState old) { base.OnStateEntry(old); actor.Velocity = default; actor.ColliderComponent.enabled = false; actor.alwaysNotGrounded = true; } public override void OnStateExit(IState old, IState newState) { base.OnStateExit(old, newState); actor.ColliderComponent.enabled = true; actor.alwaysNotGrounded = false; } public override void AfterUpdateMovement(float deltaTime) { base.AfterUpdateMovement(deltaTime); actor.Position += characterController.ViewRotation * characterController.MovementInput * ( deltaTime * (_inputActionGroup.GetAction(characterController.RunAction).IsPressed() ? 8 : characterController.initialSpeed )); actor.Rotation = Quaternion.Euler(0,characterController.LookInput.y,0); } } }