using System; using System.Collections.Generic; using BehaviorDesigner.Runtime; using DDD.Audios; using DDD.Enemies; using DDD.Interfaces; using DDD.Items; using DDD.Npcs.Crews; using DDD.Npcs.Crews.Server; using DDD.Players; using DDD.ScriptableObjects; using DDD.Tycoons; using DDD.Uis; using DDD.Uis.Tycoon; using Pathfinding; using PixelCrushers.DialogueSystem; using Sirenix.OdinInspector; using UnityEngine; using Random = UnityEngine.Random; namespace DDD.Npcs.Customers { public static class CustomerSpineAnimation { public const string Idle = "Idle"; public const string Walk = "Run"; public const string Happy = "HappyIdle"; 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 static class CatSpineAnimation { public const string Idle = "Cat/Idle"; public const string Walk = "Cat/Run"; public const string Happy = "Cat/HappyIdle"; public const string HappyRun = "Cat/HappyRun"; public const string Upset = "Cat/Upset"; public const string UpsetRun = "Cat/UpsetRun"; public const string Vomiting = "Cat/Vomiting"; public const string VomitingForm = "Cat/VomitingForm"; public const string VomitingIdle = "Cat/VomitingIdle"; public const string VomitingRun = "Cat/VomitingRun"; } public static class WitchSpineAnimation { public const string Idle = "Witch/Idle"; public const string Walk = "Witch/Run"; public const string Happy = "Witch/HappyIdle"; public const string HappyRun = "Witch/HappyRun"; public const string Upset = "Witch/Upset"; public const string UpsetRun = "Witch/UpsetRun"; } public enum CustomerInteractionType { None = 0, OrderedCook } public enum CustomerSkin { BigCat = 0, Casper = 1, CasperBlack = 2, Cat = 3, PumkinHead = 4, Reaper = 5, Witch = 6 } 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 BalloonUi BalloonUi { get; private set; } [SerializeField] private PayMoneyUi _payMoneyUiObject; [SerializeField] private Vector3 _offset = new(0f, 1.5f, 0f); // 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; set; } [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 CraftRecipeData OrderedCraftRecipeData { 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 IsOrderedCorrected { get; private set; } [field: SerializeField] public bool IsServedPlayer { get; private set; } [SerializeField] private CustomerInteractionType _customerInteractionType; public bool IsMoving { get; private set; } public bool CanVomit { get; private set; } public bool IsVomited { get; private set; } [SerializeField] private string _succeedServingSfxName = "SucceedServing"; [SerializeField] private string _failedServingSfxName = "FailedServing"; private Vector3 _currentDirection = Vector3.right; public Vector3 CurrentDirection { get => _currentDirection; set { if (value == Vector3.zero) return; _currentDirection = value; } } [Title("스킨 연출")] [field: SerializeField] public CustomerSkin CustomerSkin { get; private set; } [SerializeField] private Vector3 _bigSize = new(1.5f, 1.5f, 1.5f); [SerializeField] private Vector3 _smallSize = new(0.5f, 0.5f, 0.5f); public int HurryTime { get; private set; } private IAstarAI _astarAi; private Transform _spawnTransform; private MoneyCounter _moneyCounter; 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 OnDrawGizmosSelected() { if (!CenterTransform) return; Gizmos.color = Color.blue; Gizmos.DrawWireSphere(CenterTransform.position, InteractionRadius); } private void Awake() { InitializeComponents(); } private void Start() { EventManager.OnGaugeResetCustomers += ResetGauge; EventManager.OnPurifiedCustomerAll += Purify; string currentSkinName = SpineController.GetCurrentSkin(); foreach (CustomerSkin element in Enum.GetValues(typeof(CustomerSkin))) { if (element.ToString().Equals(currentSkinName)) { CustomerSkin = element; break; } } if (CustomerSkin is CustomerSkin.BigCat or CustomerSkin.Witch) { CanVomit = false; transform.localScale = _bigSize; } else { CanVomit = true; } } private void Update() { StateMachineController.UpdateState(this); HandleMovement(); FlipVisualLook(); } private void OnDestroy() { EventManager.OnGaugeResetCustomers -= ResetGauge; EventManager.OnPurifiedCustomerAll -= Purify; EventManager.InvokeDestroyCustomer(this); } #endregion // Initialize methods #region Initialize methods [Button("컴포넌트 초기화")] protected virtual void InitializeComponents() { if (!CenterTransform) { CenterTransform = transform; } Rigidbody = GetComponent(); CharacterCollider = GetComponent(); BehaviorTree = GetComponent(); VisualLook = transform.Find("VisualLook"); MeshRenderer = VisualLook.GetComponent(); BarkTrigger = transform.Find("DialogueSystem").GetComponent(); InteractionCanvas = transform.GetComponentInChildren(); SpineController = GetComponent(); AIMovement = GetComponent(); _astarAi = GetComponent(); } public void Initialize(LevelData levelData, Transform spawnTransform, List skins) { if (skins.Count > 0) { var randomSkin = skins[Random.Range(0, skins.Count)]; SpineController.SetSkin(randomSkin); } _moneyCounter = FindAnyObjectByType(); 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 Interaction() { switch (_customerInteractionType) { case CustomerInteractionType.None: break; case CustomerInteractionType.OrderedCook: CraftRecipeData playerPickupCook = GameManager.Instance.CurrentTycoonPlayer.TycoonPickupHandler.CurrentCraftRecipeData; //var servedCocktailData = ItemManager.Instance.CocktailDataSo.GetDataByIdx(currentPickupItem.Idx); //IsOrderedCorrected = currentPickupItem.Idx == OrderedCocktailData.Idx; IsReceivedItem = true; IsServedPlayer = true; IsOrderedCorrected = true; AudioManager.Instance.PlaySfx(_succeedServingSfxName); ServedItem(playerPickupCook); break; default: throw new ArgumentOutOfRangeException(); } } public virtual void CancelInteraction() { } public bool CanInteraction() { switch (_customerInteractionType) { case CustomerInteractionType.None: return false; case CustomerInteractionType.OrderedCook: CraftRecipeData playerPickupCook = GameManager.Instance.CurrentTycoonPlayer.TycoonPickupHandler.CurrentCraftRecipeData; return playerPickupCook?.Idx == OrderedCraftRecipeData.Idx; 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); IsOrderedCorrected = 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); EventManager.InvokeHoldInteracting(0f); } 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 OrderCook() { IsReceivedItem = false; OrderedCraftRecipeData = FindAnyObjectByType().GetRandomTodayMenu(); HurryTime = CurrentLevelData.HurryTime + TycoonManager.Instance.TycoonStatus.CustomerHurryTimeIncrease; CurrentTableSeat.OrderCook(OrderedCraftRecipeData, isReverse:true); _customerInteractionType = CustomerInteractionType.OrderedCook; RegisterPlayerInteraction(); EventManager.InvokeOrderedCocktail(this); // if (!ES3.Load(SaveData.TutorialB, false)) // { // EventManager.InvokeTutorial(TutorialName.TutorialB); // ES3.Save(SaveData.TutorialB, true); // } // // if (!ES3.Load(SaveData.TutorialJ, false) && OrderedCocktailData.Idx == "Cocktail006") // { // EventManager.InvokeTutorial(TutorialName.TutorialJ); // ES3.Save(SaveData.TutorialJ, true); // } } 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(CraftRecipeData craftRecipeData) { CurrentTableSeat.MenuBalloonUi.ReceiveItem(); if (IsOrderedCorrected) { CurrentTableSeat.SetFood(craftRecipeData.Sprite); StateMachineController.TransitionToState(HappyState, this); // int tip; // if (IsServedPlayer) // { // if (TycoonManager.Instance.TycoonStatus.ContainsPassiveCard(PassiveCard.ServingBonus)) // { // TycoonManager.Instance.TycoonStageController.ServingBonus(); // } // // tip = (int)(CurrentLevelData.Gold * TycoonManager.Instance.TycoonStatus.TipMultiplier); // } // else // { // tip = (int)(CurrentLevelData.Gold * TycoonManager.Instance.TycoonStatus.ServerTipMultiplier); // } // // if (tip > 0) // { // var payMoneyUi = Instantiate(_payMoneyUiObject, transform.position + _offset, // Quaternion.identity, TycoonUiManager.Instance.WorldCanvas.transform); // payMoneyUi.Initialize(tip, true); // } } else { StateMachineController.TransitionToState(UpsetState, this); } EventManager.InvokeServedCookToCustomer(); EventManager.InvokeServedResult(this, IsOrderedCorrected); //EventManager.InvokeSucceedServing(IsOrderedCorrected); //EventManager.InvokeCheckedSkin(CustomerSkin); } 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 = CurrentTableSeat.MenuBalloonUi.IsWaitTimeOver(); if (isWaitTimeOver) { EventManager.InvokeOrderResult(this, false); EventManager.InvokeMissedServing(); EventManager.InvokeCheckedSkin(CustomerSkin); } return isWaitTimeOver; } public void FinishFood() { var random = Random.Range(0f, 100f); if (random <= TycoonManager.Instance.TycoonStageController.StageDataSo.DirtyTablePercent) { CurrentTableSeat.DirtyTable(); } else { CurrentTableSeat.CleanTable(); } //GainExp(); } private void GainExp() { var exp = (int)(CurrentLevelData.Exp * TycoonManager.Instance.TycoonStatus.ExpMultiplier); EventManager.InvokeChangeExp(exp); } public void PayMoney() { var gold = (int)(CurrentLevelData.Gold * TycoonManager.Instance.TycoonStatus.GoldMultiplier); _moneyCounter.AddCurrentGold(gold); // if (!ES3.Load(SaveData.TutorialC, false)) // { // EventManager.InvokeTutorial(TutorialName.TutorialC); // ES3.Save(SaveData.TutorialC, true); // } } public void Vomit() { AIMovement.StopMove(); StateMachineController.TransitionToState(VomitState, this); } public void InstanceVomit() { var spawnPosition = transform.position + new Vector3(0f, 0f, -0.4f); EventManager.InvokeCreateVomiting(spawnPosition); IsVomited = true; StateMachineController.TransitionToState(IdleState, this); } public void CheckOut() { AIMovement.StopMove(); BehaviorTree.DisableBehavior(); Destroy(gameObject); } // 상호작용 메서드 public void OrderCocktail() { IsReceivedItem = false; IsOrderedCorrected = false; OrderedCocktailData = TycoonManager.Instance.TycoonIngredientController.GetRandomCocktailData(); HurryTime = CurrentLevelData.HurryTime + TycoonManager.Instance.TycoonStatus.CustomerHurryTimeIncrease; CurrentTableSeat.OrderCocktail(OrderedCocktailData.Idx, CurrentLevelData.WaitTime, HurryTime, true); _customerInteractionType = CustomerInteractionType.OrderedCook; RegisterPlayerInteraction(); EventManager.InvokeOrderedCocktail(this); if (!ES3.Load(SaveData.TutorialB, false)) { EventManager.InvokeTutorial(TutorialName.TutorialB); ES3.Save(SaveData.TutorialB, true); } if (!ES3.Load(SaveData.TutorialJ, false) && OrderedCocktailData.Idx == "Cocktail006") { EventManager.InvokeTutorial(TutorialName.TutorialJ); ES3.Save(SaveData.TutorialJ, true); } } public void MovePosition(Vector3 targetPosition) { AIMovement.Move(targetPosition); StateMachineController.TransitionToState(WalkingState, this); } public bool CanMoneyCounterInteractionPosition() { if (_moneyCounter.CenterTransform == null) return false; return AIMovement.HasReachedDestination() || Vector3.Distance(_moneyCounter.CenterTransform.position, transform.position) <= _moneyCounter.InteractionRadius; } public void MoveMoneyCounter() { VacateTable(); MovePosition(_moneyCounter.transform.position); } public void MoveSpawnPosition() { VacateTable(); MovePosition(_spawnTransform.position); } public void VacateTable() { if (CurrentTableSeat) { CurrentTableSeat.VacateSeat(); CurrentTableSeat = null; } } public void ResetGauge() { if (!IsOrderedCocktail()) return; CurrentTableSeat.MenuBalloonUi.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; public void Purify() { if (CurrentTableSeat) { CurrentTableSeat.Purify(); CurrentTableSeat = null; } if (CurrentBill) { CurrentBill.Destroy(); CurrentBill = null; } UnregisterPlayerInteraction(); CheckOut(); } #endregion } }