521 lines
16 KiB
C#
521 lines
16 KiB
C#
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 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; 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<Customer> StateMachineController { get; private set; }
|
|
public IStateMachine<Customer> IdleState { get; private set; }
|
|
public IStateMachine<Customer> WalkingState { get; private set; }
|
|
public IStateMachine<Customer> HappyState { get; private set; }
|
|
public IStateMachine<Customer> UpsetState { get; private set; }
|
|
public IStateMachine<Customer> VomitState { get; private set; }
|
|
|
|
public event Action OnInteractionCompleted;
|
|
|
|
#endregion
|
|
|
|
// Unity events
|
|
|
|
#region Unity events
|
|
|
|
private void Awake()
|
|
{
|
|
InitializeComponents();
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
EventManager.OnGaugeResetCustomers += ResetGauge;
|
|
EventManager.OnPurifiedCustomerAll += Purify;
|
|
}
|
|
|
|
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()
|
|
{
|
|
CenterTransform = transform;
|
|
Rigidbody = GetComponent<Rigidbody>();
|
|
CharacterCollider = GetComponent<CapsuleCollider>();
|
|
BehaviorTree = GetComponent<BehaviorTree>();
|
|
VisualLook = transform.Find("VisualLook");
|
|
MeshRenderer = VisualLook.GetComponent<MeshRenderer>();
|
|
BarkTrigger = transform.Find("DialogueSystem").GetComponent<BarkTrigger>();
|
|
InteractionCanvas = transform.GetComponentInChildren<InteractionCanvas>();
|
|
BalloonUi = InteractionCanvas.transform.GetComponentInChildren<BalloonUi>();
|
|
|
|
SpineController = GetComponent<SpineController>();
|
|
AIMovement = GetComponent<AiMovement>();
|
|
|
|
_astarAi = GetComponent<IAstarAI>();
|
|
}
|
|
|
|
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<Customer>(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);
|
|
|
|
var tip = 0;
|
|
if (IsServedPlayer)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
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);
|
|
|
|
EventManager.InvokeChangeExp(exp);
|
|
}
|
|
|
|
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;
|
|
|
|
public void Purify()
|
|
{
|
|
if (CurrentTableSeat)
|
|
{
|
|
CurrentTableSeat.Purify();
|
|
CurrentTableSeat = null;
|
|
}
|
|
|
|
if (CurrentBill)
|
|
{
|
|
CurrentBill.Destroy();
|
|
CurrentBill = null;
|
|
}
|
|
|
|
UnregisterPlayerInteraction();
|
|
CheckOut();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |