using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Code.Game.OfficeGame.BattleMode; using Code.Infrastructure.Camera; using Code.OfficeGame.BattleMode.Actions; using Code.OfficeGame.BattleMode.Actions.Deciders; using Code.OfficeGame.BattleMode.UI; using Cysharp.Threading.Tasks; using R3; using UnityEngine; namespace Code.OfficeGame.BattleMode.Timeline { public class BattleTimeline { public const int TicksPerSecond = 60; public const float TickDelta = 1f / TicksPerSecond; private readonly ReactiveProperty _currentBattleFrame = new(0); public ReadOnlyReactiveProperty CurrentBattleFrame => _currentBattleFrame; private readonly BattleTimelinePanel _timelinePanel; private List _entities; private DisposableBag _battleSubscriptions; private Dictionary _battleActions; private Dictionary _battleActionDeciders; private Dictionary _actionDecisionResults = new(); private Dictionary _battleActionDatas = new(); private BattleActionDecisionPanel _battleActionDecisionPanel; private CameraStateMachine _cameraStateMachine; public BattleTimeline( BattleTimelinePanel timelinePanel, IEnumerable battleActions, IEnumerable battleActionDeciders, BattleActionDecisionPanel battleActionDecisionPanel, CameraStateMachine cameraStateMachine) { _cameraStateMachine = cameraStateMachine; _battleActionDecisionPanel = battleActionDecisionPanel; _timelinePanel = timelinePanel; _battleActions = battleActions.ToDictionary(x=>x.ActionType); _battleActionDeciders = battleActionDeciders.ToDictionary(x => x.DeciderType); } public async UniTask RunBattleLoopAsync(List entities, CancellationToken ctx) { _entities = entities; _currentBattleFrame.Value = 0; _battleSubscriptions = new DisposableBag(); try { _currentBattleFrame .Subscribe(_timelinePanel.OnFrameTick) .AddTo(ref _battleSubscriptions); // foreach (var entity in entities) // entity.OnActionSet // .Subscribe(action => // { // action.Frame += _currentBattleFrame.Value; // _timelinePanel.SetAction(action); // }) // .AddTo(ref _battleSubscriptions); await DecideActions(_entities, ctx); while (!ctx.IsCancellationRequested) { List entitiesNeedOrders = await PlayTimelineUntilPause(ctx); await DecideActions(entitiesNeedOrders, ctx); } } finally { _battleSubscriptions.Dispose(); } } private async UniTask DecideActions(List entities, CancellationToken ctx) { foreach (var entity in entities) { Debug.Log($"Deciding action for {entity.name}"); var entityId = entity.GetEntityId(); _actionDecisionResults.Remove(entityId); _actionDecisionResults[entityId] = await DecideAction(ctx, entity); _battleActionDatas.Remove(entityId); var decidedAction = _actionDecisionResults[entityId]; _battleActionDatas[entityId] = _battleActions[decidedAction.ActionType] .MountAndGetFrames(entity, decidedAction); } } private async UniTask> PlayTimelineUntilPause(CancellationToken ctx) { float accumulator = 0f; var readyEntities = new List(); while (readyEntities.Count == 0 && !ctx.IsCancellationRequested) { await UniTask.Yield(PlayerLoopTiming.Update, ctx); accumulator += Time.unscaledDeltaTime; while (accumulator >= TickDelta) { accumulator -= TickDelta; SimulateSingleFrame(readyEntities); if (readyEntities.Count > 0) return readyEntities; } } return readyEntities; } private void SimulateSingleFrame(List outReadyEntities) { _currentBattleFrame.Value++; foreach (var entity in _entities) { var entityId = entity.GetEntityId(); var finished = _battleActions[_actionDecisionResults[entityId].ActionType] .Tick( TickDelta, entity, _actionDecisionResults[entityId], _battleActionDatas[entityId]); entity.animator.Update(TickDelta); if (finished) { outReadyEntities.Add(entity); } } Physics.Simulate(TickDelta); Physics.SyncTransforms(); } private async UniTask DecideAction(CancellationToken ctx, EntityProperties entity) { _battleActionDecisionPanel.SetEntityInfo(entity.battleActionTypes); BattleActionType decidedAction = BattleActionType.None; BattleActionDecisionResult decision = null; while (decidedAction == BattleActionType.None || ctx.IsCancellationRequested) { Cursor.lockState = CursorLockMode.Confined; ((CameraBattleModeState)_cameraStateMachine.CurrentBaseState).battleDeciding = true; await _battleActionDecisionPanel.Open(ctx); decidedAction = await _battleActionDecisionPanel.GetDecision(ctx); if (decidedAction == BattleActionType.None) continue; var deciderType = _battleActions[decidedAction].DeciderType; _battleActionDeciders[deciderType].MountDecider(); while (!ctx.IsCancellationRequested) { Cursor.lockState = CursorLockMode.Locked; ((CameraBattleModeState)_cameraStateMachine.CurrentBaseState).battleDeciding = false; await _battleActionDecisionPanel.Close(ctx); decision = await _battleActionDeciders[deciderType].GetDecision(); if (decision.ResultCode == BattleActionDecisionResultCode.CancelDecision) decidedAction = BattleActionType.None; if (decision.ResultCode != BattleActionDecisionResultCode.Deciding) break; } _battleActionDeciders[deciderType].UnmountDecider(); } if (decision == null) throw new ArgumentNullException(nameof(decision)); decision.ActionType = decidedAction; return decision; } } }