using System; using BehaviorDesigner.Runtime; using BlueWater.Enemies; using BlueWater.Interfaces; using BlueWater.Items; using BlueWater.Npcs.Crews; using BlueWater.Npcs.Crews.Server; using BlueWater.Players; using BlueWater.Tycoons; using BlueWater.Uis; using Pathfinding; using PixelCrushers.DialogueSystem; using Sirenix.OdinInspector; using UnityEngine; using Random = UnityEngine.Random; namespace BlueWater.Npcs.Customers { public static class CustomerSpineAnimation { public const string Idle = "Idle"; public const string Walk = "Run"; public const string Happy = "Happy"; public const string HappyRun = "HappyRun"; public const string Upset = "Upset"; public const string UpsetRun = "UpsetRun"; public const string Vomiting = "Vomiting"; public const string VomitingForm = "VomitingForm"; public const string VomitingIdle = "VomitingIdle"; public const string VomitingRun = "VomitingRun"; } public enum CustomerInteractionType { None = 0, ServedCocktail } public class Customer : MonoBehaviour, IPlayerInteraction, ICrewInteraction { // Variables #region Variables // Components [field: SerializeField] public Transform CenterTransform { get; private set; } [field: SerializeField] public Rigidbody Rigidbody { get; private set; } [field: SerializeField] public CapsuleCollider CharacterCollider { get; private set; } [field: SerializeField] public BehaviorTree BehaviorTree { get; private set; } [field: SerializeField] public Transform VisualLook { get; private set; } [field: SerializeField] public MeshRenderer MeshRenderer { get; private set; } [field: SerializeField] public BarkTrigger BarkTrigger { get; private set; } [field: SerializeField] public InteractionCanvas InteractionCanvas { get; private set; } [field: SerializeField] public ParticleSystem PayMoneyParticle { get; private set; } [field: SerializeField] public BalloonUi BalloonUi { get; private set; } [field: SerializeField] public PayMoneyUi PayMoneyUi { get; private set; } // Classes [field: SerializeField, Required] public SpineController SpineController { get; private set; } [field: SerializeField, Required] public AiMovement AIMovement { get; private set; } [field: SerializeField] public bool EnableInteraction { get; private set; } = true; [field: SerializeField] public float InteractionRadius { get; private set; } = 2f; [field: SerializeField] public string InteractionMessage { get; private set; } [SerializeField] private Vomiting _vomiting; [field: Title("실시간 데이터")] [field: SerializeField] public LevelData CurrentLevelData { get; private set; } [field: SerializeField] public TableSeat CurrentTableSeat { get; private set; } [field: SerializeField] public CocktailData OrderedCocktailData { get; private set; } [field: SerializeField] public Bill CurrentBill { get; private set; } [field: SerializeField] public bool IsMatchedServer { get; private set; } [field: SerializeField] public bool IsReceivedItem { get; private set; } [field: SerializeField] public bool IsOrderedSucceed { get; private set; } [field: SerializeField] public bool IsServedPlayer { get; private set; } [SerializeField] private CustomerInteractionType _customerInteractionType; public bool IsMoving { get; private set; } public bool IsVomited { get; private set; } private Vector3 _currentDirection = Vector3.right; public Vector3 CurrentDirection { get => _currentDirection; set { if (value == Vector3.zero) return; _currentDirection = value; } } private IAstarAI _astarAi; private Transform _spawnTransform; private int _paidAmount; private int _foodPrice; private int _tipAmount; // State public StateMachineController StateMachineController { get; private set; } public IStateMachine IdleState { get; private set; } public IStateMachine WalkingState { get; private set; } public IStateMachine HappyState { get; private set; } public IStateMachine UpsetState { get; private set; } public IStateMachine VomitState { get; private set; } public event Action OnInteractionCompleted; #endregion // Unity events #region Unity events private void Awake() { InitializeComponents(); } private void Start() { EventManager.OnGaugeResetCustomers += ResetGauge; } private void Update() { StateMachineController.UpdateState(this); HandleMovement(); FlipVisualLook(); } private void OnDestroy() { EventManager.OnGaugeResetCustomers -= ResetGauge; EventManager.InvokeDestroyCustomer(this); } #endregion // Initialize methods #region Initialize methods [Button("컴포넌트 초기화")] protected virtual void InitializeComponents() { CenterTransform = transform; Rigidbody = GetComponent(); CharacterCollider = GetComponent(); BehaviorTree = GetComponent(); VisualLook = transform.Find("VisualLook"); MeshRenderer = VisualLook.GetComponent(); BarkTrigger = transform.Find("DialogueSystem").GetComponent(); PayMoneyParticle = transform.Find("PayMoneyParticle").GetComponent(); InteractionCanvas = transform.GetComponentInChildren(); BalloonUi = InteractionCanvas.transform.GetComponentInChildren(); PayMoneyUi = InteractionCanvas.transform.GetComponentInChildren(); SpineController = GetComponent(); AIMovement = GetComponent(); _astarAi = GetComponent(); } public void Initialize(LevelData levelData, Transform spawnTransform) { CurrentLevelData = levelData; _spawnTransform = spawnTransform; IdleState = new IdleState(); WalkingState = new WalkingState(); HappyState = new HappyState(); UpsetState = new UpsetState(); VomitState = new VomitState(); StateMachineController = new StateMachineController(this, IdleState); BehaviorTree.EnableBehavior(); } #endregion // Methods #region Methods private void HandleMovement() { if (!_astarAi.canMove || _astarAi.isStopped) { IsMoving = false; return; } CurrentDirection = _astarAi.velocity.normalized; IsMoving = _astarAi.velocity != Vector3.zero || _astarAi.velocity != Vector3.positiveInfinity; } private void FlipVisualLook() { var localScale = VisualLook.localScale; localScale.x = CurrentDirection.x switch { > 0.01f => -Mathf.Abs(localScale.x), < -0.01f => Mathf.Abs(localScale.x), _ => localScale.x }; VisualLook.localScale = localScale; } public void SetTableSeat(TableSeat tableSeat) { CurrentTableSeat = tableSeat; } public void SetCurrentDirection(Vector3 normalDirection) => CurrentDirection = normalDirection; public void SetTableSeatPositionAndDirection() { transform.position = CurrentTableSeat.SeatTransform.position; CurrentTableSeat.OccupySeat(); CurrentTableSeat.UnreserveSeat(); SetCurrentDirection(CurrentTableSeat.TableDirection); } public void ServedItem(CocktailData cocktailData) { BalloonUi.ReceiveItem(cocktailData); if (IsOrderedSucceed) { CurrentTableSeat.SetFood(); StateMachineController.TransitionToState(HappyState, this); } else { StateMachineController.TransitionToState(UpsetState, this); } EventManager.InvokeCocktailServedToCustomer(cocktailData, IsServedPlayer); EventManager.InvokeOrderResult(this, IsOrderedSucceed); } public void Interaction() { switch (_customerInteractionType) { case CustomerInteractionType.None: break; case CustomerInteractionType.ServedCocktail: var currentPickupItem = GameManager.Instance.CurrentTycoonPlayer.TycoonPickupHandler.GetCurrentPickupItem(); var servedCocktailData = ItemManager.Instance.CocktailDataSo.GetDataByIdx(currentPickupItem.Idx); IsOrderedSucceed = currentPickupItem.Idx == OrderedCocktailData.Idx; IsReceivedItem = true; IsServedPlayer = true; ServedItem(servedCocktailData); break; default: throw new ArgumentOutOfRangeException(); } } public virtual void CancelInteraction() { } public bool CanInteraction() { switch (_customerInteractionType) { case CustomerInteractionType.None: return false; case CustomerInteractionType.ServedCocktail: var currentPickupItem = GameManager.Instance.CurrentTycoonPlayer.TycoonPickupHandler.GetCurrentPickupItem(); return currentPickupItem != null; default: throw new ArgumentOutOfRangeException(); } } public void InteractionCrew(Crew crew) { var serverCrew = (ServerCrew)crew; var currentPickupItem = serverCrew.CurrentPickupItem; var servedCocktailData = ItemManager.Instance.CocktailDataSo.GetDataByIdx(currentPickupItem.Idx); IsOrderedSucceed = currentPickupItem.Idx == OrderedCocktailData.Idx; IsReceivedItem = true; IsServedPlayer = false; ServedItem(servedCocktailData); serverCrew.BalloonUi.DiscardItem(); serverCrew.ResetMission(); } public void CancelInteractionCrew() { throw new NotImplementedException(); } public bool CanInteractionCrew(Crew crew = null) { return IsOrderedCocktail(); } public virtual void ShowInteractionUi() { SpineController.EnableCustomMaterial(); EventManager.InvokeShowInteractionUi(InteractionMessage); } public virtual void HideInteractionUi() { SpineController.DisableCustomMaterial(); EventManager.InvokeHideInteractionUi(); } public void RegisterPlayerInteraction() { if (EnableInteraction) { GameManager.Instance.CurrentTycoonPlayer.TycoonInput.RegisterPlayerInteraction(this); } } public void UnregisterPlayerInteraction() { if (EnableInteraction) { GameManager.Instance.CurrentTycoonPlayer.TycoonInput.UnregisterPlayerInteraction(this); } _customerInteractionType = CustomerInteractionType.None; } public void Bark(string conversation, BarkOrder barkOrder = BarkOrder.Random) { if (string.IsNullOrEmpty(conversation)) return; BarkTrigger.barkOrder = barkOrder; BarkTrigger.conversation = conversation; BarkTrigger.OnUse(); } public bool IsWaitTimeOver() { var isWaitTimeOver = BalloonUi.IsWaitTimeOver(); if (isWaitTimeOver) { EventManager.InvokeOrderResult(this, false); } return isWaitTimeOver; } public void PayMoney() { var random = Random.Range(0f, 100f); if (random <= TycoonManager.Instance.TycoonStageController.StageDataSo.DirtyTablePercent) { CurrentTableSeat.DirtyTable(); } else { CurrentTableSeat.CleanTable(); } var exp = (int)(CurrentLevelData.Exp * TycoonManager.Instance.TycoonStatus.ExpMultiplier); var gold = (int)(CurrentLevelData.Gold * TycoonManager.Instance.TycoonStatus.GoldMultiplier); var tip = (int)(CurrentLevelData.Gold * TycoonManager.Instance.TycoonStatus.GoldMultiplier); PayMoneyParticle.Play(); PayMoneyUi.PayMoney(gold); EventManager.InvokeChangeExp(exp); TycoonManager.Instance.TycoonStatus.CurrentGold += gold; } public void Vomit() { AIMovement.StopMove(); StateMachineController.TransitionToState(VomitState, this); } public void InstanceVomit() { var spawnPosition = transform.position + new Vector3(0f, 0f, 0.1f); Instantiate(_vomiting, spawnPosition, _vomiting.transform.rotation); IsVomited = true; StateMachineController.TransitionToState(IdleState, this); } public void CheckOut() { AIMovement.StopMove(); BehaviorTree.DisableBehavior(); Destroy(gameObject); } // 상호작용 메서드 public void OrderCocktail() { IsReceivedItem = false; IsOrderedSucceed = false; InteractionMessage = "칵테일 전달"; OrderedCocktailData = TycoonManager.Instance.TycoonIngredientController.GetRandomCocktailData(); var hurryTime = CurrentLevelData.HurryTime + TycoonManager.Instance.TycoonStatus.CustomerHurryTimeIncrease; BalloonUi.OrderItem(OrderedCocktailData.Idx, CurrentLevelData.WaitTime, hurryTime); _customerInteractionType = CustomerInteractionType.ServedCocktail; RegisterPlayerInteraction(); EventManager.InvokeOrderedCocktail(this); } public void MoveSpawnPosition() { if (CurrentTableSeat) { CurrentTableSeat.VacateSeat(); CurrentTableSeat = null; } AIMovement.Move(_spawnTransform.position); StateMachineController.TransitionToState(WalkingState, this); } public void ResetGauge() { if (!IsOrderedCocktail()) return; BalloonUi.ResetGauge(); CurrentBill.ResetGauge(); } public bool IsOrderedCocktail() { return CurrentTableSeat && CurrentTableSeat.IsOccupied && !IsReceivedItem; } public void TryMatchedServer() { if (!CanInteractionCrew()) return; IsMatchedServer = true; } public void SetCurrentBill(Bill bill) => CurrentBill = bill; #endregion } }