using System.Collections.Generic; using UnityEngine; namespace OnlyScove.Scripts { [RequireComponent(typeof(CharacterController), typeof(InputReader), typeof(Animator))] public class PlayerStateMachine : MonoBehaviour { [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; } public float VelocityY { get; set; } public bool IsGrounded { get; private set; } public bool WasGrounded { get; private set; } // Interaction system variables private List interactablesNearby = new List(); private int currentInteractableIndex = 0; public string CurrentStateName => currentState != null ? currentState.GetType().Name : "None"; private PlayerBaseState currentState; private bool hasControl = true; protected virtual void Awake() { Controller = GetComponent(); Input = GetComponent(); Anim = GetComponentInChildren(); Scanner = GetComponent(); Cam = Camera.main?.GetComponent(); } private void Start() { SwitchState(new PlayerIdleState(this)); // Subscribe to cycle events Input.OnNextInteractEvent += OnNextInteract; Input.OnPreviousInteractEvent += OnPreviousInteract; } private void OnDestroy() { if (Input != null) { Input.OnNextInteractEvent -= OnNextInteract; Input.OnPreviousInteractEvent -= OnPreviousInteract; } } protected virtual void Update() { if (!hasControl) return; WasGrounded = IsGrounded; CheckGround(); UpdateInteractablesList(); currentState?.Tick(Time.deltaTime); } private void FixedUpdate() { if (!hasControl) return; currentState?.PhysicsTick(Time.fixedDeltaTime); } private void CheckGround() { IsGrounded = Physics.CheckSphere(transform.TransformPoint(GroundCheckOffset), GroundCheckRadius, GroundMask); } private void UpdateInteractablesList() { interactablesNearby.Clear(); Collider[] colliders = Physics.OverlapSphere(transform.position + transform.forward * (InteractionRange / 2), InteractionRange, InteractionMask); foreach (var col in colliders) { if (col.TryGetComponent(out IInteractable interactable)) { if (!interactablesNearby.Contains(interactable)) interactablesNearby.Add(interactable); } } if (interactablesNearby.Count == 0) { currentInteractableIndex = 0; } else if (currentInteractableIndex >= interactablesNearby.Count) { currentInteractableIndex = interactablesNearby.Count - 1; } } private void OnNextInteract() { if (interactablesNearby.Count <= 1) return; currentInteractableIndex = (currentInteractableIndex + 1) % interactablesNearby.Count; Debug.Log($"[Interaction] Switched to: {interactablesNearby[currentInteractableIndex].InteractionPrompt}"); } private void OnPreviousInteract() { if (interactablesNearby.Count <= 1) return; currentInteractableIndex--; if (currentInteractableIndex < 0) currentInteractableIndex = interactablesNearby.Count - 1; Debug.Log($"[Interaction] Switched to: {interactablesNearby[currentInteractableIndex].InteractionPrompt}"); } 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); Gizmos.color = Color.blue; Gizmos.DrawWireSphere(transform.position + transform.forward * (InteractionRange / 2), InteractionRange); } } }