using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Code.Game.Entities; 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> 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; _currentBattleFrame.Value++; SimulateSingleFrame(readyEntities, _entities); if (readyEntities.Count > 0) return readyEntities; } } return readyEntities; } public async UniTask PreviewBattleLoopAsync(int frameCount, List entities, CancellationToken ctx) { while (!ctx.IsCancellationRequested) { Dictionary actionDataSnapshots = new(); Dictionary resultsSnapshots = new(); Dictionary entitySnapshots = new(); foreach (var entity in entities) { var entityId = entity.GetEntityId(); if (!_actionDecisionResults.TryGetValue(entityId, out var actionDecisionResult) || !_battleActionDatas.TryGetValue(entityId, out var battleActionData)) continue; resultsSnapshots[entityId] = actionDecisionResult.GetSnapshot(); actionDataSnapshots[entityId] = battleActionData.GetSnapshot(); entitySnapshots[entityId] = entity.GetSnapshot(); } try { await PlayTimelineForCertainFrames(frameCount, ctx).AttachExternalCancellation(ctx); } finally { foreach (var entity in entities) { var entityId = entity.GetEntityId(); if (!resultsSnapshots.TryGetValue(entityId, out var actionDecisionResult) || !actionDataSnapshots.TryGetValue(entityId, out var battleActionData)) continue; if (!_actionDecisionResults.TryAdd(entityId, actionDecisionResult)) _actionDecisionResults[entityId]?.RestoreSnapshot(actionDecisionResult); if (!_battleActionDatas.TryAdd(entityId, battleActionData)) _battleActionDatas[entityId]?.RestoreSnapshot(battleActionData); entity.RestoreSnapshot(entitySnapshots[entityId]); } } } } private async UniTask PlayTimelineForCertainFrames(int frameCount, CancellationToken ctx) { float accumulator = 0f; var readyEntities = new List(); while (frameCount > 0 && !ctx.IsCancellationRequested) { await UniTask.Yield(PlayerLoopTiming.Update, ctx); accumulator += Time.unscaledDeltaTime; while (accumulator >= TickDelta) { accumulator -= TickDelta; frameCount--; SimulateSingleFrame(readyEntities, _entities); if (readyEntities.Count > 0) { foreach (var entity in readyEntities) { var entityID = entity.GetEntityId(); _battleActionDatas.Remove(entityID); _actionDecisionResults.Remove(entityID); } } } } } private void SimulateSingleFrame(List outReadyEntities, List inEntities) { foreach (var entity in inEntities) { var entityId = entity.GetEntityId(); if (!_actionDecisionResults.TryGetValue(entityId, out var actionDecisionResult) || !_battleActionDatas.TryGetValue(entityId, out var battleActionData)) continue; var finished = _battleActions[actionDecisionResult.ActionType] .Tick( TickDelta, entity, actionDecisionResult, battleActionData); entity.animator.Update(TickDelta); if (finished) { outReadyEntities.Add(entity); } } Physics.Simulate(TickDelta); Physics.SyncTransforms(); } private async UniTask DecideActions(List entities, CancellationToken ctx) { foreach (var entity in entities) { Debug.Log($"Deciding action for {entity.name}"); var entityId = entity.GetEntityId(); var (decisionResult, battleActionData) = await DecideAction(ctx, entity); _actionDecisionResults[entityId] = decisionResult; _battleActionDatas[entityId] = battleActionData; } } private async UniTask<(BattleActionDecisionResult, BattleActionData)> DecideAction(CancellationToken ctx, EntityProperties entity) { _battleActionDecisionPanel.SetEntityInfo(entity.battleActionTypes); BattleActionType decidedActionType = BattleActionType.None; BattleActionDecisionResult decisionResult = null; BattleActionData actionData = null; var entityId = entity.GetEntityId(); BattleActionData entityActionDataSnapshot = null; BattleActionDecisionResult entityResultSnapshot = null; if (_battleActionDatas.TryGetValue(entityId, out var battleActionData)) entityActionDataSnapshot = battleActionData.GetSnapshot(); if (_actionDecisionResults.TryGetValue(entityId, out var actionDecisionResult)) entityResultSnapshot = actionDecisionResult.GetSnapshot(); while (decidedActionType == BattleActionType.None || ctx.IsCancellationRequested) { Cursor.lockState = CursorLockMode.Confined; ((CameraBattleModeState)_cameraStateMachine.CurrentBaseState).battleDeciding = true; await _battleActionDecisionPanel.Open(ctx); decidedActionType = await _battleActionDecisionPanel.GetDecision(ctx); if (decidedActionType == BattleActionType.None) continue; var deciderType = _battleActions[decidedActionType].DeciderType; _battleActionDeciders[deciderType].MountDecider(); CancellationTokenSource previewCancellationToken = new(); while (!ctx.IsCancellationRequested) { Cursor.lockState = CursorLockMode.Locked; ((CameraBattleModeState)_cameraStateMachine.CurrentBaseState).battleDeciding = false; await _battleActionDecisionPanel.Close(ctx); decisionResult = await _battleActionDeciders[deciderType].GetDecision(); decisionResult.ActionType = decidedActionType; Debug.Log(decisionResult.ResultCode); actionData = _battleActions[decidedActionType].MountAndGetFrames(entity, decisionResult); previewCancellationToken.Cancel(); previewCancellationToken.Dispose(); if (decisionResult.ResultCode == BattleActionDecisionResultCode.Deciding) { _actionDecisionResults[entityId] = decisionResult; _battleActionDatas[entityId] = actionData; previewCancellationToken = new(); PreviewBattleLoopAsync(actionData.TotalFrames, _entities, previewCancellationToken.Token) .Forget(); } else if (decisionResult.ResultCode == BattleActionDecisionResultCode.CancelDecision) { decidedActionType = BattleActionType.None; break; } else break; } _battleActionDeciders[deciderType].UnmountDecider(); } if (entityResultSnapshot != null && !_actionDecisionResults.TryAdd(entityId, entityResultSnapshot)) _actionDecisionResults[entityId]?.RestoreSnapshot(entityResultSnapshot); if (entityActionDataSnapshot != null && !_battleActionDatas.TryAdd(entityId, entityActionDataSnapshot)) _battleActionDatas[entityId]?.RestoreSnapshot(entityActionDataSnapshot); if (decisionResult == null || actionData == null) throw new ArgumentNullException(nameof(decisionResult)); return (decisionResult, actionData); } } }