using Assets.Scripts.Actions.Interfaces; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using UnityEngine; using UnityEngine.AI; public class BlockingAnimation : Attribute { } public enum Tasks { Move, Interact, Rotate }; public enum TaskStatus { Waiting, InProgress, Complete }; public enum AnimationStates { [EnumMember(Value = "Idle")] Idle, [EnumMember(Value = "Move")] Walking, [EnumMember(Value = "Sleeping")] Sleeping, [EnumMember(Value = "StandToSit")] [BlockingAnimation] Sitting, [EnumMember(Value = "SitToStand")] [BlockingAnimation] Standing }; public class Player : MonoBehaviour { public static Player Instance { get; private set; } [SerializeField] public NavMeshAgent _navAgent; [SerializeField] public Animator _animator; [SerializeField] private Transform _holdPoint; private AnimationStates _currentAnimation; private Vector3 _groundDeltaPosition; public Dictionary Stats; public IWorkPlace WorkPlace { get; set; } private readonly Queue _tasks = new Queue(); private PlayerTasks _currentTask; private const string WALK_VELOCITY = "WalkVelocity"; private FoodObject _foodObject; private void Awake() { if (Instance != null) { Debug.Log("There's more than one player instance"); return; } Instance = this; } private void Start() { TimeManager.OnMinuteChanged += UpdateStatsByClock; _navAgent.updatePosition = false; Stats = PlayerStats.CreateInitialStats(); } private void OnDestroy() { TimeManager.OnMinuteChanged -= UpdateStatsByClock; } private void Update() { if (IsBlockingAnimation(_currentAnimation)) { if (IsAnimationStatePlaying(0, GetEnumMemberValue(_currentAnimation))) return; } if (_currentTask == null || _currentTask.Status == TaskStatus.Complete) { _tasks.TryDequeue(out _currentTask); } if (_currentTask != null) { if (_currentTask.Status == TaskStatus.Waiting) Debug.Log($"Current task {_currentTask.Task}"); switch (_currentTask.Task) { case Tasks.Rotate: _currentTask.UpdateStatus(Rotate(_currentTask.TagretObject._playerArrivePoint.forward)); break; case Tasks.Move: if (_currentAnimation == AnimationStates.Sitting) { SetPlayerAnimation(AnimationStates.Standing); return; } _navAgent.SetDestination(_currentTask.TagretObject._playerArrivePoint.position); _currentTask.UpdateStatus(MoveToPoint()); break; case Tasks.Interact: if (pathComplete(_currentTask.TagretObject._playerArrivePoint.position)) _currentTask.UpdateStatus(InteractWithObject(_currentTask.TagretObject)); else { AddTask(new PlayerTasks(Tasks.Move, _currentTask.TagretObject)); AddTask(new PlayerTasks(Tasks.Rotate, _currentTask.TagretObject)); AddTask(_currentTask); _currentTask = null; } break; } } } private TaskStatus MoveToPoint() { SetPlayerAnimation(AnimationStates.Walking); var worldDeltaPosition = _navAgent.nextPosition - transform.position; _groundDeltaPosition.x = Vector3.Dot(transform.right, worldDeltaPosition); _groundDeltaPosition.y = Vector3.Dot(transform.forward, worldDeltaPosition); Vector2 velocity = (Time.deltaTime > 1e-5f) ? _groundDeltaPosition / Time.deltaTime : Vector2.zero; _animator.SetFloat(WALK_VELOCITY, velocity.y); return pathComplete(_navAgent.destination) ? TaskStatus.Complete : TaskStatus.InProgress; } private bool pathComplete(Vector3 destination) { if (Vector3.Distance(destination, _navAgent.transform.position) <= _navAgent.radius) { if (!_navAgent.hasPath || _navAgent.velocity.sqrMagnitude < 0.2f) { SetPlayerAnimation(AnimationStates.Idle); return true; } } return false; } public TaskStatus Rotate(Vector3 target) { var targetRot = Quaternion.LookRotation(target); Quaternion rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(target), 10 * Time.deltaTime); rotation.x = 0; transform.rotation = rotation; if (IsApproximate(targetRot, transform.rotation, 0.000004f)) { return TaskStatus.Complete; } return TaskStatus.InProgress; } public static bool IsApproximate(Quaternion q1, Quaternion q2, float precision) { return Mathf.Abs(Quaternion.Dot(q1, q2)) >= 1 - precision; } private TaskStatus InteractWithObject(BaseInteractableObject interactableObject) { interactableObject.Interact(this); return TaskStatus.Complete; } public void SetPlayerAnimation(AnimationStates newState) { if (newState == _currentAnimation) { return; } _animator.Play(GetEnumMemberValue(newState)); _currentAnimation = newState; } private bool IsAnimationStatePlaying(int animLayer, string stateName) { if (_animator.GetCurrentAnimatorStateInfo(animLayer).IsName(stateName) && _animator.GetCurrentAnimatorStateInfo(animLayer).normalizedTime < 1.0f) return true; else return false; } private void OnAnimatorMove() { transform.position = _navAgent.nextPosition; } public void AddTask(PlayerTasks task) { _tasks.Enqueue(task); } public void UpdateStatsByClock() { Stats[StatsId.Food].deduct(0.034m); // 48 hours it's 100, 100/2880=~0.034 per minute if (_currentAnimation != AnimationStates.Sleeping) { Stats[StatsId.Energy].deduct(0.1m); // 24 hours it's 100, 100/1440=~0.096 per minute } else { Stats[StatsId.Energy].increase(1m); } } public void BuyAction(IPlayerAction action) { Stats[StatsId.Money].deduct(((ISellable)action).Price); action.ApplyAction(this); } private static string GetEnumMemberValue(T value) where T : struct, IConvertible { return typeof(T) .GetTypeInfo() .DeclaredMembers .SingleOrDefault(x => x.Name == value.ToString()) ?.GetCustomAttribute(false) ?.Value; } private static bool IsBlockingAnimation(T value) where T : struct, IConvertible { var enumType = typeof(T); var memInfo = enumType.GetMember(value.ToString()); var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType().FirstOrDefault(); return attr != null; } public void SetFoodObject(FoodObject foodObject) { foodObject.transform.parent = _holdPoint; foodObject.transform.localPosition = Vector3.zero; _foodObject = foodObject; Debug.Log($"player hold {foodObject.GetFoodObjectSO().name}"); } public FoodObject GetFoodObject() { return _foodObject; } public void ClearFoodObject() { Destroy(_foodObject.gameObject); _foodObject = null; } public bool HasFoodObject() { return _foodObject != null; } }