using System.Threading; using Code.Game.Gameplay.Player; using Code.Infrastructure.Camera; using Code.Infrastructure.Input; using Cysharp.Threading.Tasks; using KinematicCharacterController; using UnityEngine; using UnityEngine.AI; using UnityEngine.InputSystem; namespace Code.OfficeGame.BattleMode.Actions { public class BattleActionMove : BattleAction { private CameraStateMachine _cameraStateMachine; private IInputService _inputService; private PlayerMovementController _playerMovementController; private readonly NavMeshPath _path = new(); private Vector3[] _corners; private int _currentCornerIndex; private Vector3 _targetPosition; private LayerMask _navMeshMask; private const float StoppingDistance = 0.15f; public BattleActionMove( CameraStateMachine cameraStateMachine, IInputService inputService, PlayerMovementController playerMovementController) { _cameraStateMachine = cameraStateMachine; _inputService = inputService; _playerMovementController = playerMovementController; _navMeshMask = LayerMask.GetMask("Environment"); } public override void Tick(float tickDelta) { if (Finished) return; if (_corners == null || _currentCornerIndex >= _corners.Length) { StopMovementAndFinish(); return; } Vector3 currentPos = _playerMovementController.Motor.TransientPosition; while (_currentCornerIndex < _corners.Length) { Vector3 toCorner = _corners[_currentCornerIndex] - currentPos; toCorner.y = 0f; if (toCorner.sqrMagnitude <= StoppingDistance * StoppingDistance) { _currentCornerIndex++; } else { break; } } if (_currentCornerIndex >= _corners.Length) { StopMovementAndFinish(); KinematicCharacterSystem.Simulate( tickDelta, KinematicCharacterSystem.CharacterMotors, KinematicCharacterSystem.PhysicsMovers ); return; } Vector3 targetCorner = _corners[_currentCornerIndex]; Vector3 moveDirection = targetCorner - currentPos; moveDirection.y = 0f; float distance = moveDirection.magnitude; Vector3 moveNormal = distance > 0.0001f ? moveDirection / distance : Vector3.zero; float maxStep = _playerMovementController.MaxStableMoveSpeed * tickDelta; float inputMagnitude = (_currentCornerIndex == _corners.Length - 1 && distance < maxStep && maxStep > 0f) ? distance / maxStep : 1f; var aiInputs = new AICharacterInputs { MoveVector = moveNormal * inputMagnitude, Look = moveNormal.sqrMagnitude > 0.0001f ? Quaternion.LookRotation(moveNormal, _playerMovementController.Motor.CharacterUp) : _playerMovementController.Motor.TransientRotation }; _playerMovementController.SetInputs(ref aiInputs); KinematicCharacterSystem.Simulate( tickDelta, KinematicCharacterSystem.CharacterMotors, KinematicCharacterSystem.PhysicsMovers ); Vector3 postMovePos = _playerMovementController.Motor.TransientPosition; Vector3 toFinal = _corners[^1] - postMovePos; toFinal.y = 0f; if (_currentCornerIndex >= _corners.Length - 1 && toFinal.sqrMagnitude <= StoppingDistance * StoppingDistance) { StopMovementAndFinish(); } } private void StopMovementAndFinish() { var emptyInputs = new AICharacterInputs { MoveVector = Vector3.zero, Look = _playerMovementController.Motor.TransientRotation }; _playerMovementController.SetInputs(ref emptyInputs); _playerMovementController.Motor.BaseVelocity = Vector3.zero; Finished = true; } private CancellationTokenSource _waitForEnterTokenSource; private BattleActionDecisionResultCode _decisionResultCode; public override async UniTask DecideAction() { _waitForEnterTokenSource = new CancellationTokenSource(); _decisionResultCode = BattleActionDecisionResultCode.CancelDecision; _targetPosition = _playerMovementController.transform.position; _inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select, SelectPosition); _inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm, ConfirmPosition); _inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel, CancelPosition); await UniTask.WaitUntilCanceled(_waitForEnterTokenSource.Token).SuppressCancellationThrow(); _inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select, SelectPosition); _inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm, ConfirmPosition); _inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel, CancelPosition); _waitForEnterTokenSource.Dispose(); _waitForEnterTokenSource = null; Finished = _decisionResultCode != BattleActionDecisionResultCode.Decided; return _decisionResultCode; } private void CancelPosition(InputAction.CallbackContext obj) { _decisionResultCode = BattleActionDecisionResultCode.CancelDecision; _waitForEnterTokenSource?.Cancel(); } private void ConfirmPosition(InputAction.CallbackContext input) { Vector3 startPos = _playerMovementController.Motor.TransientPosition; if ((_targetPosition - startPos).sqrMagnitude > StoppingDistance * StoppingDistance && NavMesh.CalculatePath(startPos, _targetPosition, NavMesh.AllAreas, _path) && _path.corners.Length > 0) { _corners = _path.corners; _currentCornerIndex = _corners.Length > 1 ? 1 : 0; _decisionResultCode = BattleActionDecisionResultCode.Decided; } else _decisionResultCode = BattleActionDecisionResultCode.Deciding; _waitForEnterTokenSource?.Cancel(); } private void SelectPosition(InputAction.CallbackContext input) { var position = _cameraStateMachine.Camera.transform.position; var direction = _cameraStateMachine.Camera.transform.forward; var didHit = Physics.Raycast(position, direction, out RaycastHit hit, Mathf.Infinity, _navMeshMask); if (!didHit) return; Debug.Log($"Hit {hit.point}"); _targetPosition = hit.point; } } }