using System.Collections.Generic; using UnityEngine; using Fusion; namespace OnlyScove.Scripts { [RequireComponent(typeof(CharacterController), typeof(InputReader), typeof(Animator))] public class PlayerStateMachine : NetworkBehaviour { [field: Header("References")] [field: SerializeField] public CharacterController Controller { get; private set; } [field: SerializeField] public virtual InputReader Input { get; private set; } [field: SerializeField] public Animator Anim { get; private set; } [field: SerializeField] public EnvironmentScanner Scanner { get; private set; } public CameraController Cam { get; private set; } [field: Header("Movement Settings")] [field: SerializeField] public float WalkSpeed { get; private set; } = 3f; [field: SerializeField] public float RunSpeed { get; private set; } = 6f; [field: SerializeField] public float SprintSpeed { get; private set; } = 9f; // 150% of RunSpeed [field: SerializeField] public float SneakSpeed { get; private set; } = 1.5f; [field: SerializeField] public float DashForce { get; private set; } = 10f; [field: SerializeField] public float RotationSpeed { get; private set; } = 500f; [field: SerializeField] public float AnimationDamping { get; private set; } = 0.2f; [field: Header("Airborne Settings")] [field: SerializeField] public float JumpHeight { get; private set; } = 2f; [field: SerializeField] public float Gravity { get; private set; } = -9.81f; [field: SerializeField] public float ThrustDownwardForce { get; private set; } = -20f; [field: Header("Ground Check")] [field: SerializeField] public float GroundCheckRadius { get; private set; } = 0.2f; [field: SerializeField] public Vector3 GroundCheckOffset { get; private set; } [field: SerializeField] public LayerMask GroundMask { get; private set; } [field: Header("Interaction")] [field: SerializeField] public float InteractionRange { get; private set; } = 2f; [field: SerializeField] public LayerMask InteractionMask { get; private set; } [Networked] public Quaternion NetworkedCameraRotation { get; set; } public float VelocityY { get; set; } public bool IsGrounded { get; private set; } public bool WasGrounded { get; private set; } private List interactablesNearby = new List(); private int currentInteractableIndex = 0; public string CurrentStateName => currentState != null ? currentState.GetType().Name : "None"; public static PlayerStateMachine Local { get; private set; } // THÊM DÒNG NÀY private PlayerBaseState currentState; private bool hasControl = true; protected virtual void Awake() { Controller = GetComponent(); Input = GetComponent(); Anim = GetComponentInChildren(); Scanner = GetComponent(); } public override void Spawned() { // BẮT BUỘC: Mọi máy (Server và Client) đều phải khởi tạo trạng thái ban đầu SwitchState(new PlayerIdleState(this)); if (Object.HasInputAuthority) { Local = this; CameraController cameraController = GameObject.FindAnyObjectByType(); if (cameraController != null) { Cam = cameraController; Cam.followTarget = this.transform; Cam.inputReader = this.Input; } Input.OnNextInteractEvent += OnNextInteract; Input.OnPreviousInteractEvent += OnPreviousInteract; } } public override void FixedUpdateNetwork() { if (Object == null) return; // 1. NHẬN DỮ LIỆU TỪ MẠNG if (GetInput(out NetworkInputData data)) { // Gán phím bấm vào InputReader để các State (Move, Jump...) sử dụng Input.ApplyNetworkInput(data.move, data.sprint); // CẬP NHẬT HƯỚNG CAMERA (Cho cả Server và Client) // Đây là mấu chốt để Server tính toán hướng chạy đúng NetworkedCameraRotation = data.rot; } // 2. CHẶN MÁY KHÁCH KHÁC, NHƯNG CHO PHÉP SERVER VÀ LOCAL PLAYER CHẠY LOGIC if (!Object.HasInputAuthority && !Runner.IsServer) return; if (!hasControl) return; WasGrounded = IsGrounded; CheckGround(); UpdateInteractablesList(); currentState?.Tick(Runner.DeltaTime); } protected virtual void Update() { } private void CheckGround() { IsGrounded = Physics.CheckSphere(transform.TransformPoint(GroundCheckOffset), GroundCheckRadius, GroundMask); } private void UpdateInteractablesList() { interactablesNearby.Clear(); IInteractable target = Scanner.ScanForInteractable(InteractionRange, InteractionMask); if (target != null) interactablesNearby.Add(target); currentInteractableIndex = 0; } private void OnNextInteract() { if (interactablesNearby.Count <= 1) return; currentInteractableIndex = (currentInteractableIndex + 1) % interactablesNearby.Count; } private void OnPreviousInteract() { if (interactablesNearby.Count <= 1) return; currentInteractableIndex--; if (currentInteractableIndex < 0) currentInteractableIndex = interactablesNearby.Count - 1; } public IInteractable GetInteractable() { if (interactablesNearby.Count == 0) return null; return interactablesNearby[currentInteractableIndex]; } public void SetGroundCheck(float radius, Vector3 offset) { GroundCheckRadius = radius; GroundCheckOffset = offset; } public void SwitchState(PlayerBaseState newState) { currentState?.Exit(); currentState = newState; currentState?.Enter(); } public void SetControl(bool control) { hasControl = control; Controller.enabled = control; if (!control) Anim.SetFloat("Speed", 0f); } private void OnDrawGizmosSelected() { Gizmos.color = new Color(0, 1, 0, 0.5f); Gizmos.DrawSphere(transform.TransformPoint(GroundCheckOffset), GroundCheckRadius); } } }