Files
Hasebe/Assets/Code/OfficeGame/BattleMode/Timeline/BattleTimeline.cs
T
2026-10-30 02:58:51 +03:00

209 lines
7.6 KiB
C#

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<int> _currentBattleFrame = new(0);
public ReadOnlyReactiveProperty<int> CurrentBattleFrame => _currentBattleFrame;
private readonly BattleTimelinePanel _timelinePanel;
private List<EntityProperties> _entities;
private DisposableBag _battleSubscriptions;
private Dictionary<BattleActionType, BattleAction> _battleActions;
private Dictionary<DeciderType, BattleActionDecider> _battleActionDeciders;
private Dictionary<EntityId, BattleActionDecisionResult> _actionDecisionResults = new();
private Dictionary<EntityId, BattleActionData> _battleActionDatas = 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 = battleActions.ToDictionary(x=>x.ActionType);
_battleActionDeciders = battleActionDeciders.ToDictionary(x => x.DeciderType);
}
public async UniTask RunBattleLoopAsync(List<EntityProperties> 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<EntityProperties> entitiesNeedOrders = await PlayTimelineUntilPause(ctx);
await DecideActions(entitiesNeedOrders, ctx);
}
}
finally
{
_battleSubscriptions.Dispose();
}
}
private async UniTask DecideActions(List<EntityProperties> 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<List<EntityProperties>> PlayTimelineUntilPause(CancellationToken ctx)
{
float accumulator = 0f;
var readyEntities = new List<EntityProperties>();
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<EntityProperties> 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<BattleActionDecisionResult> 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;
}
}
}