using System.Threading; using Code.Game.Gameplay.Player; using Code.Infrastructure.Camera; using Code.Infrastructure.Input; using Code.OfficeGame.BattleMode.Actions.Deciders; using Code.OfficeGame.BattleMode.Actions.Player; using Code.OfficeGame.BattleMode.Marker; using Code.OfficeGame.BattleMode.Timeline; using Cysharp.Threading.Tasks; using KinematicCharacterController; using Reflex.Attributes; using UnityEngine; using UnityEngine.AI; using UnityEngine.InputSystem; namespace Code.OfficeGame.BattleMode.Actions { public class BattleActionMove : BattleAction { private const float StoppingDistance = 0.15f; public override bool Tick( float tickDelta, EntityProperties entity, BattleActionDecisionResult battleActionDecisionResult, BattleActionData battleActionData) { var movementActionData = (MovementBattleActionData) battleActionData; var entityMovementController = entity.MovementController; if (movementActionData.Corners == null || movementActionData.CurrentCornerIndex >= movementActionData.Corners.Length) { StopMovementAndFinish(entityMovementController); return true; } Vector3 currentPos = entity.KinematicCharacterMotor.TransientPosition; while (movementActionData.CurrentCornerIndex < movementActionData.Corners.Length) { Vector3 toCorner = movementActionData.Corners[movementActionData.CurrentCornerIndex] - currentPos; toCorner.y = 0f; if (toCorner.sqrMagnitude <= StoppingDistance * StoppingDistance) movementActionData.CurrentCornerIndex++; else break; } if (movementActionData.CurrentCornerIndex >= movementActionData.Corners.Length) { StopMovementAndFinish(entity.MovementController); KinematicCharacterSystem.Simulate( tickDelta, KinematicCharacterSystem.CharacterMotors, KinematicCharacterSystem.PhysicsMovers ); return true; } Vector3 targetCorner = movementActionData.Corners[movementActionData.CurrentCornerIndex]; Vector3 moveDirection = targetCorner - currentPos; moveDirection.y = 0f; float distance = moveDirection.magnitude; Vector3 moveNormal = distance > 0.0001f ? moveDirection / distance : Vector3.zero; float maxStep = entityMovementController.MaxStableMoveSpeed * tickDelta; float inputMagnitude = (movementActionData.CurrentCornerIndex == movementActionData.Corners.Length - 1 && distance < maxStep && maxStep > 0f) ? distance / maxStep : 1f; // TODO Для последнего кадра должен быть особый Look совпадающий с маркером var aiInputs = new AICharacterInputs { MoveVector = moveNormal * inputMagnitude, Look = moveNormal.sqrMagnitude > 0.0001f ? Quaternion.LookRotation(moveNormal, entityMovementController.Motor.CharacterUp) : entityMovementController.Motor.TransientRotation }; entityMovementController.SetInputs(ref aiInputs); KinematicCharacterSystem.Simulate( tickDelta, KinematicCharacterSystem.CharacterMotors, KinematicCharacterSystem.PhysicsMovers ); Vector3 postMovePos = entityMovementController.Motor.TransientPosition; Vector3 toFinal = movementActionData.Corners[^1] - postMovePos; toFinal.y = 0f; if (movementActionData.CurrentCornerIndex >= movementActionData.Corners.Length - 1 && toFinal.sqrMagnitude <= StoppingDistance * StoppingDistance) { StopMovementAndFinish(entityMovementController); return true; } return false; } private void StopMovementAndFinish(MovementController movementController) { Debug.Log("MovementFinished"); var emptyInputs = new AICharacterInputs { MoveVector = Vector3.zero, Look = movementController.Motor.TransientRotation }; movementController.SetInputs(ref emptyInputs); movementController.Motor.BaseVelocity = Vector3.zero; } public override BattleActionType ActionType => BattleActionType.Move; public override DeciderType DeciderType => DeciderType.Position; public override BattleActionData MountAndGetFrames(EntityProperties entity, BattleActionDecisionResult result) { MovementBattleActionData movementBattleActionData = new(); Vector3 startPos = entity.KinematicCharacterMotor.TransientPosition; NavMeshPath path = new(); var positionDecisionResult = (PositionBattleActionDeciderResult) result; if ((positionDecisionResult.Position - startPos).sqrMagnitude > StoppingDistance * StoppingDistance && NavMesh.CalculatePath(startPos, positionDecisionResult.Position, NavMesh.AllAreas, path) && path.corners.Length > 0) { movementBattleActionData.Corners = path.corners; movementBattleActionData.CurrentCornerIndex = movementBattleActionData.Corners.Length > 1 ? 1 : 0; movementBattleActionData.TotalFrames = CalculatePathFrames(movementBattleActionData.Corners, entity.MovementController); } else movementBattleActionData.TotalFrames = 0; return movementBattleActionData; } private int CalculatePathFrames(Vector3[] corners, MovementController movementController) { if (corners == null || corners.Length < 2) return 0; float totalDistanceXZ = 0f; for (int i = 1; i < corners.Length; i++) { Vector3 segment = corners[i] - corners[i - 1]; segment.y = 0f; totalDistanceXZ += segment.magnitude; } float effectiveDistance = Mathf.Max(0f, totalDistanceXZ - StoppingDistance); float stepPerFrame = movementController.MaxStableMoveSpeed * BattleTimeline.TickDelta; if (stepPerFrame <= 0.0001f) return 0; return Mathf.CeilToInt(effectiveDistance / stepPerFrame); } } }