using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Sirenix.OdinInspector; using UnityEngine; namespace DDD { [CreateAssetMenu(fileName = "RestaurantCustomerStateSo", menuName = "RestaurantState/RestaurantCustomerStateSo")] public class RestaurantCustomerStateSo : ScriptableObject, IGameFlowHandler { [Title("스폰 제어")] [Tooltip("플로우 시작 후 첫 손님이 등장하기까지 대기 시간(초)")] [SerializeField] private float _firstSpawnDelaySeconds = 5f; [SerializeField] private Vector3 _spawnPoint = new(5f, 0f, 4f); [Title("디버그")] [SerializeField] private SpawnSchedule _spawnSchedule; private GameStateSo _gameStateSo; private LevelDataSo _levelDataSo; private CustomerDataSo _customerDataSo; private CustomerPoolDataSo _customerPoolDataSo; private ICustomerFactory _iCustomerFactory; private CancellationTokenSource _spawnLoopCancellationTokenSource; public async Task OnReadyNewFlow(GameFlowState newFlowState) { if (newFlowState == GameFlowState.RunRestaurant) { await InitializeRunRestaurant(); } } public Task OnExitCurrentFlow(GameFlowState exitingFlowState) { if (exitingFlowState == GameFlowState.RunRestaurant) { _spawnLoopCancellationTokenSource?.Cancel(); _spawnLoopCancellationTokenSource?.Dispose(); _spawnLoopCancellationTokenSource = null; } return Task.CompletedTask; } private async Task InitializeRunRestaurant() { _gameStateSo = await AssetManager.LoadAsset(DataConstants.GameStateSo); Debug.Assert(_gameStateSo != null, "_gameStateSo is null"); _iCustomerFactory = new CustomerFactory(); var currentGameLevel = _gameStateSo.GetCurrentLevel(); if (_levelDataSo == null) { _levelDataSo = DataManager.Instance.GetDataSo(); } if (_customerDataSo == null) { _customerDataSo = DataManager.Instance.GetDataSo(); } if (_customerPoolDataSo == null) { _customerPoolDataSo = DataManager.Instance.GetDataSo(); } var currentLevelData = _levelDataSo.GetDataList().FirstOrDefault(data => data.Level == currentGameLevel); Debug.Assert(currentLevelData != null, "currentLevelData is null"); var normalPool = _customerPoolDataSo.GetDataById(currentLevelData.CustomerPool); var specialPool = _customerPoolDataSo.GetDataById(currentLevelData.SpecialCustomerPool); _spawnLoopCancellationTokenSource?.Cancel(); _spawnLoopCancellationTokenSource = new CancellationTokenSource(); _ = RunSpawnLoopAsync(currentLevelData, normalPool, specialPool, _spawnLoopCancellationTokenSource.Token); } private async Task RunSpawnLoopAsync(LevelData levelData, CustomerPoolData normalPool, CustomerPoolData specialPool, CancellationToken token) { if (_firstSpawnDelaySeconds > 0) await Task.Delay(TimeSpan.FromSeconds(_firstSpawnDelaySeconds), token); var scheduleBuilder = CreateBuilder(levelData.SpawnType); int randomSeed = Environment.TickCount; SpawnSchedule MakeSchedule() => scheduleBuilder.Build(new SpawnScheduleBuildArgs { NormalIds = (IReadOnlyList) (normalPool?.ValidCustomers) ?? Array.Empty(), SpecialIds = (IReadOnlyList) (specialPool?.ValidCustomers) ?? Array.Empty(), NormalQuota = Math.Max(0, normalPool?.CustomerLimitCount ?? 0), SpecialQuota = Math.Max(0, specialPool?.CustomerLimitCount ?? 0), Seed = ++randomSeed }); _spawnSchedule = MakeSchedule(); float wait = Mathf.Max(0.1f, levelData.CustomerRespawnTime); while (token.IsCancellationRequested == false) { if (_spawnSchedule.TryDequeue(out var customerId) == false) break; if (_customerDataSo.TryGetDataById(customerId, out var customerData)) { var rotation = Quaternion.identity; _ = _iCustomerFactory.CreateAsync(new CustomerSpawnArgs { CustomerDataId = customerId, CustomerData = customerData, Position = _spawnPoint, Rotation = rotation, Parent = null }); } await Task.Delay(TimeSpan.FromSeconds(wait), token); } } private ISpawnScheduleBuilder CreateBuilder(SpawnType type) { return type switch { SpawnType.Random => new RandomSpawnScheduleBuilder(), SpawnType.Regular => new RegularSpawnScheduleBuilder(), _ => new RandomSpawnScheduleBuilder() }; } } }