467 lines
15 KiB
C#
467 lines
15 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 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; 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; set; }
|
|
|
|
[field: SerializeField]
|
|
public bool IsMatchedServer { get; set; }
|
|
|
|
[field: SerializeField]
|
|
public bool IsReceivedItem { get; set; }
|
|
|
|
[field: SerializeField]
|
|
public bool IsOrderedSucceed { get; set; }
|
|
|
|
[SerializeField]
|
|
private CustomerInteractionType _customerInteractionType;
|
|
|
|
public bool IsMoving { get; private set; }
|
|
public bool IsVomited { get; 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;
|
|
|
|
private bool _isQuitting;
|
|
|
|
// 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 OnApplicationQuit()
|
|
{
|
|
_isQuitting = true;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
StateMachineController.UpdateState(this);
|
|
HandleMovement();
|
|
FlipVisualLook();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
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>();
|
|
PayMoneyParticle = transform.Find("PayMoneyParticle").GetComponent<ParticleSystem>();
|
|
InteractionCanvas = transform.GetComponentInChildren<InteractionCanvas>();
|
|
BalloonUi = InteractionCanvas.transform.GetComponentInChildren<BalloonUi>();
|
|
PayMoneyUi = InteractionCanvas.transform.GetComponentInChildren<PayMoneyUi>();
|
|
|
|
SpineController = GetComponent<SpineController>();
|
|
AIMovement = GetComponent<AiMovement>();
|
|
|
|
_astarAi = GetComponent<IAstarAI>();
|
|
}
|
|
|
|
public void Initialize(LevelData levelData, Transform spawnTransform)
|
|
{
|
|
CurrentLevelData = levelData;
|
|
_spawnTransform = spawnTransform;
|
|
|
|
IdleState = new IdleStateMachine();
|
|
WalkingState = new WalkingStateMachine();
|
|
HappyState = new HappyStateMachine();
|
|
UpsetState = new UpsetStateMachine();
|
|
VomitState = new VomitStateMachine();
|
|
|
|
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;
|
|
BalloonUi.Initialize(CurrentTableSeat);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
ServedItem(servedCocktailData);
|
|
EventManager.InvokeCocktailServedToCustomer(servedCocktailData);
|
|
EventManager.InvokeOrderResult(this, IsOrderedSucceed);
|
|
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;
|
|
ServedItem(servedCocktailData);
|
|
serverCrew.BalloonUi.DiscardItem();
|
|
serverCrew.ResetMission();
|
|
EventManager.InvokeOrderResult(this, IsOrderedSucceed);
|
|
}
|
|
|
|
public void CancelInteractionCrew()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public bool CanInteractionCrew(Crew crew = null)
|
|
{
|
|
return !IsReceivedItem && CurrentTableSeat;
|
|
}
|
|
|
|
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);
|
|
|
|
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();
|
|
BalloonUi.OrderItem(OrderedCocktailData.Idx, CurrentLevelData.WaitTime, CurrentLevelData.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);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |