Files
Hasebe/Assets/Code/OfficeGame/BattleMode/Timeline/BattleTimeline.cs
T

370 lines
14 KiB
C#

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<int> _currentBattleFrame = new(0);
public ReadOnlyReactiveProperty<int> CurrentBattleFrame => _currentBattleFrame;
private readonly BattleTimelinePanel _timelinePanel;
private DisposableBag _battleSubscriptions;
private Dictionary<BattleActionType, BattleAction> _battleActions;
private Dictionary<DeciderType, BattleActionDecider> _battleActionDeciders;
private Dictionary<EntityId, EntityProperties> _entities;
private Dictionary<EntityId, BattleActionData> _battleActionDatas = new();
private Dictionary<EntityId, BattleActionDecisionResult> _actionDecisionResults = new();
private BattleActionDecisionPanel _battleActionDecisionPanel;
private CameraStateMachine _cameraStateMachine;
public BattleTimeline(
BattleTimelinePanel timelinePanel,
IEnumerable<BattleAction> battleActions,
IEnumerable<BattleActionDecider> battleActionDeciders,
BattleActionDecisionPanel battleActionDecisionPanel,
CameraStateMachine cameraStateMachine)
{
_cameraStateMachine = cameraStateMachine;
_battleActionDecisionPanel = battleActionDecisionPanel;
_timelinePanel = timelinePanel;
_battleActions = new();
foreach (var battleAction in battleActions)
{
battleAction.SetBattleTimeline(this);
_battleActions.Add(battleAction.ActionType, battleAction);
}
_battleActionDeciders = battleActionDeciders.ToDictionary(x => x.DeciderType);
}
public async UniTask RunBattleLoopAsync(List<EntityProperties> entities, CancellationToken ctx)
{
_entities = entities.ToDictionary(x => x.GetEntityId());
_currentBattleFrame.Value = 0;
_battleSubscriptions = new DisposableBag();
try
{
_currentBattleFrame
.Subscribe(_timelinePanel.OnFrameTick)
.AddTo(ref _battleSubscriptions);
await DecideActions(_entities.Keys.ToArray(), ctx);
while (!ctx.IsCancellationRequested)
{
EntityId[] entitiesNeedOrders = await PlayTimelineUntilPause(ctx);
await DecideActions(entitiesNeedOrders, ctx);
}
}
finally
{
_battleSubscriptions.Dispose();
}
}
private async UniTask<EntityId[]> PlayTimelineUntilPause(CancellationToken ctx)
{
float accumulator = 0f;
while (!ctx.IsCancellationRequested)
{
await UniTask.Yield(PlayerLoopTiming.Update, ctx);
accumulator += Mathf.Min(Time.unscaledDeltaTime, 0.1f);
while (accumulator >= TickDelta)
{
accumulator -= TickDelta;
_currentBattleFrame.Value++;
var readyEntities = SimulateSingleFrame(_entities);
if (readyEntities.Length > 0) return readyEntities;
}
}
return null;
}
// TODO ЗАПУСКАТЬ ДВА ЦИКЛА ПОЛНЫЙ И ДО ПРЕРЫВАНИЯ
private async UniTask PreviewBattleLoopAsync(int frameCount,
Dictionary<EntityId, EntityProperties> entities,
CancellationToken ctx)
{
Dictionary<EntityId, BattleActionData> actionDataSnapshots = new();
Dictionary<EntityId, BattleActionDecisionResult> resultsSnapshots = new();
Dictionary<EntityId, EntitySnapshot> entitySnapshots = new();
foreach (var (entityId, entity) in entities)
{
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
{
while (!ctx.IsCancellationRequested)
{
RestoreFromSnapshots(entities, resultsSnapshots, actionDataSnapshots, entitySnapshots);
await PlayTimelineForCertainFrames(frameCount, ctx).AttachExternalCancellation(ctx);
}
}
finally
{
RestoreFromSnapshots(entities, resultsSnapshots, actionDataSnapshots, entitySnapshots);
}
}
private void RestoreFromSnapshots(Dictionary<EntityId, EntityProperties> entities,
Dictionary<EntityId, BattleActionDecisionResult> resultsSnapshots,
Dictionary<EntityId, BattleActionData> actionDataSnapshots,
Dictionary<EntityId, EntitySnapshot> entitySnapshots)
{
foreach (var (entityId, entity) in entities)
{
if (!resultsSnapshots.TryGetValue(entityId, out var actionDecisionResult)
|| !actionDataSnapshots.TryGetValue(entityId, out var battleActionData))
continue;
bool actionChanged = false;
if (!_actionDecisionResults.TryAdd(entityId, actionDecisionResult))
{
actionChanged = actionDecisionResult.ActionType
!= _actionDecisionResults[entityId].ActionType;
if (actionChanged)
_actionDecisionResults[entityId] = actionDecisionResult.GetSnapshot();
else
_actionDecisionResults[entityId]?.RestoreSnapshot(actionDecisionResult);
}
if (!_battleActionDatas.TryAdd(entityId, battleActionData))
{
if (actionChanged)
_battleActionDatas[entityId] = battleActionData.GetSnapshot();
else _battleActionDatas[entityId]?.RestoreSnapshot(battleActionData);
}
entity.RestoreSnapshot(entitySnapshots[entityId]);
}
}
private async UniTask PlayTimelineForCertainFrames(int frameCount, CancellationToken ctx)
{
float accumulator = 0f;
while (frameCount > 0 && !ctx.IsCancellationRequested)
{
bool isCanceled = await UniTask.Yield(PlayerLoopTiming.Update, ctx).SuppressCancellationThrow();
if (isCanceled) break;
accumulator += Mathf.Min(Time.unscaledDeltaTime, 0.1f);
while (accumulator >= TickDelta && frameCount > 0 && !ctx.IsCancellationRequested)
{
accumulator -= TickDelta;
frameCount--;
var readyEntities = SimulateSingleFrame(_entities);
if (readyEntities.Length > 0)
{
foreach (var entityId in readyEntities)
{
// _battleActionDatas.Remove(entityID);
// _actionDecisionResults.Remove(entityID);
_battleActionDatas[entityId] = new WaitBattleActionData {
TotalFrames = frameCount,
WaitFrames = frameCount
};
_actionDecisionResults[entityId] = new FrameBattleActionDeciderResult
{
ActionType = BattleActionType.Wait,
FrameCount = frameCount,
ResultCode = BattleActionDecisionResultCode.Decided
};
}
// return;
}
}
}
}
private EntityId[] SimulateSingleFrame(Dictionary<EntityId, EntityProperties> inEntities)
{
foreach (var entity in inEntities)
{
var entityId = entity.Key;
if (!_actionDecisionResults.TryGetValue(entityId, out var actionDecisionResult)
|| !_battleActionDatas.TryGetValue(entityId, out var battleActionData))
continue;
_battleActions[actionDecisionResult.ActionType]
.Tick(
TickDelta,
entity.Value,
actionDecisionResult,
battleActionData);
entity.Value.animator.Update(TickDelta);
}
Physics.Simulate(TickDelta);
Physics.SyncTransforms();
var readyEntities = _battleActionDatas
.Where(x => x.Value.Finished)
.Select(x => x.Key)
.ToArray();
return readyEntities;
}
private async UniTask DecideActions(EntityId[] entities, CancellationToken ctx)
{
foreach (var entityId in entities)
{
var (decisionResult, battleActionData) = await DecideAction(ctx, _entities[entityId]);
_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();
_timelinePanel.SetPreviousActionText(entityResultSnapshot.ActionType.ToString());
}
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(entity);
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;
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);
}
public void ForceDecideAction(
EntityId entityId,
BattleActionData forcedBattleActionData,
BattleActionDecisionResult forcedBattleActionDataResult)
{
_battleActionDatas[entityId] = forcedBattleActionData;
_actionDecisionResults[entityId] = forcedBattleActionDataResult;
Debug.Log($"Задано действие {forcedBattleActionDataResult.ActionType} для {entityId}");
}
}
}