using System; using System.Collections.Generic; using SepCore.Battle; using SepCore.Definition; using SepCore.UI; using UnityEngine; using UnityGameFramework.Runtime; namespace SepCore.CustomComponent { /// /// 单局战斗组件,也是战斗模块的唯一外部入口。 /// 负责本局临时角色状态、计时、探索暂停标志、战斗占用和当前唯一的 BattleRuntime。 /// 外部通过 TryStartBattle 启动战斗、CloseBattle 关闭调试战斗; /// 战斗规则(调度、指令校验、效果、敌人决策)在内部共享同一个 BattleRuntime, /// 不为模块内部的函数调用建立请求/响应 DTO。 /// 本局种子与共享随机源由独立的 RandomComponent 持有,战斗只沿用,不创建私有随机源。 /// public class TurnBattleComponent : GameFrameworkComponent { private readonly List _players = new List(); private float _elapsedMs; private bool _timerPaused; private bool _explorationPaused; private bool _battleActive; private BattleRuntime _runtime; private IBattleConfigProvider _config; private Action _onCompleted; private Action _stepListener; private Coroutine _autoAdvanceRoutine; /// /// 敌人自动行动前的间歇时间(秒),用于让 UI 展示行动过程。 /// private const float AutoAdvanceDelaySeconds = 0.6f; /// /// 获取本局临时角色状态列表(只读)。 /// public IReadOnlyList Players => _players; /// /// 获取本局已流逝时间(毫秒)。 /// public long RunElapsedMs => (long)_elapsedMs; /// /// 获取单局计时是否已暂停。 /// public bool IsTimerPaused => _timerPaused; /// /// 获取探索更新是否已暂停。 /// 战斗期间为 true,地图更新统一以此作为门禁。 /// public bool IsExplorationPaused => _explorationPaused; /// /// 获取当前是否存在战斗占用。 /// 战斗重复进入由此标志拒绝。 /// public bool IsBattleActive => _battleActive; /// /// 获取当前唯一的战斗运行时;仅战斗期间非 null。 /// internal BattleRuntime Runtime => _runtime; /// /// 开始一局新的单局:重置全部临时状态。 /// public void BeginRun() { ResetRunState(); } /// /// 结束当前单局,清空全部临时状态。 /// public void EndRun() { ResetRunState(); } /// /// 用指定状态替换本局临时角色列表,保持战备顺序。 /// /// 新的临时角色状态,可为空。 public void ReplacePlayers(IEnumerable players) { _players.Clear(); if (players != null) { _players.AddRange(players); } } /// /// 尝试开始一场战斗,是战斗模块的唯一外部入口。 /// 校验通过后预留战斗占用、暂停探索更新与单局计时并打开 BattleForm; /// 校验失败不消耗随机数、不修改单局状态。 /// 战斗完成时通过 onCompleted 一次性回调返回 BattleResult;调试入口可不提供回调。 /// /// 探索层创建的遭遇输入。 /// 战斗结束的一次性完成回调,可为 null。 /// 是否成功开始。 public bool TryStartBattle(BattleEncounter encounter, Action onCompleted) { if (encounter == null) { Log.Error("Can not start battle with null encounter."); return false; } if (_battleActive) { Log.Warning("Can not start battle because battle occupancy is already active."); return false; } if (GameEntry.Random == null || GameEntry.Random.Random == null) { Log.Warning("Can not start battle because run random source is not initialized."); return false; } if (_players.Count == 0) { Log.Warning("Can not start battle because no run player state exists."); return false; } if (GameEntry.Luban == null || !GameEntry.Luban.IsReady) { Log.Warning("Can not start battle because battle config is not ready."); return false; } if (_config == null) { _config = new LubanBattleConfigProvider(); } BattleRuntime runtime = BattleRuntime.Create(encounter, _players, _config, GameEntry.Random.Random); if (runtime == null) { Log.Warning("Can not start battle because battle runtime can not be created from given encounter/players/config."); return false; } _runtime = runtime; _onCompleted = onCompleted; _battleActive = true; _explorationPaused = true; _timerPaused = true; GameEntry.UI.OpenUIForm(UIFormType.BattleForm); Log.Info("Battle started with encounter '{0}'.", encounter.EncounterId); // M2:敌人速度更高时开局行动者为敌人,同样按间歇节奏自动推进 ScheduleAutoAdvance(); return true; } /// /// 提交当前玩家指令并同步推进单次行动,返回本次推进的行动记录、最新视图和可能的最终结果。 /// 非法指令不消耗行动机会;玩家行动后若轮到敌人,由组件按间歇节奏自动推进敌人回合, /// 每一步通过已注册的推进监听推送给 UI。 /// 战斗完成后向 onCompleted 一次性回调返回 BattleResult。 /// /// 当前玩家行动者的指令。 /// 本次玩家行动的推进结果;当前没有进行中的战斗时返回 null。 public BattleStep SubmitCommand(BattleCommand command) { if (_runtime == null || _runtime.IsCompleted) { return null; } BattleStep step = _runtime.SubmitCommand(command); if (step.Result != null) { ApplyResultWriteback(step.Result); Action callback = _onCompleted; _onCompleted = null; callback?.Invoke(step.Result); } else { ScheduleAutoAdvance(); } return step; } /// /// 注册战斗推进监听;UI 通过它接收组件自动推进(敌人行动)产生的每一步。 /// /// 推进监听,传入 null 取消注册。 public void SetStepListener(Action listener) { _stepListener = listener; } /// /// 获取当前战斗的只读视图;没有进行中的战斗时返回 null。 /// public BattleViewState GetViewState() { return _runtime != null ? _runtime.BuildViewState() : null; } /// /// 关闭当前战斗壳层并恢复探索更新与单局计时。 /// M0 调试入口专用;M5 起由战斗结果统一回写单局状态后关闭。 /// public void CloseBattle() { if (!_battleActive) { return; } UGuiForm form = GameEntry.UI.GetUIForm(UIFormType.BattleForm); if (form != null) { GameEntry.UI.CloseUIForm(form); } _runtime = null; _onCompleted = null; StopAutoAdvance(); _stepListener = null; _explorationPaused = false; _timerPaused = false; _battleActive = false; Log.Info("Battle closed."); } /// /// 设置探索更新暂停状态。 /// /// 是否暂停探索更新。 public void SetExplorationPaused(bool paused) { _explorationPaused = paused; } /// /// 设置单局计时暂停状态。 /// /// 是否暂停单局计时。 public void SetTimerPaused(bool paused) { _timerPaused = paused; } private void Update() { if (_timerPaused) { return; } _elapsedMs += Time.deltaTime * 1000f; } /// /// 当前行动者是敌人且战斗未结束时,启动带间歇的自动推进协程。 /// private void ScheduleAutoAdvance() { if (_autoAdvanceRoutine != null) { return; } if (_runtime == null || _runtime.IsCompleted || _runtime.CurrentActor == null || _runtime.CurrentActor.Faction != BattleFaction.Enemy) { return; } _autoAdvanceRoutine = StartCoroutine(AutoAdvanceRoutine()); } private System.Collections.IEnumerator AutoAdvanceRoutine() { // 战斗 UI 打开是异步资源加载,等推进监听注册后再推第一步,避免敌人先手事件丢失 int waitFrames = 0; while (_stepListener == null && _runtime != null && !_runtime.IsCompleted && waitFrames++ < 600) { yield return null; } while (_runtime != null && !_runtime.IsCompleted && _runtime.CurrentActor != null && _runtime.CurrentActor.Faction == BattleFaction.Enemy) { yield return new WaitForSeconds(AutoAdvanceDelaySeconds); BattleStep step = _runtime.AdvanceEnemyTurn(); if (step.Result != null) { ApplyResultWriteback(step.Result); Action callback = _onCompleted; _onCompleted = null; callback?.Invoke(step.Result); } _stepListener?.Invoke(step); } _autoAdvanceRoutine = null; } private void StopAutoAdvance() { if (_autoAdvanceRoutine != null) { StopCoroutine(_autoAdvanceRoutine); _autoAdvanceRoutine = null; } } /// /// 非 TotalDefeat 结果把玩家战后 HP/MP 回写到单局临时状态。 /// 首版保留原始战斗值;M5 按结束矩阵统一应用阵亡恢复 1/1 等规则。 /// private void ApplyResultWriteback(BattleResult result) { if (result.Outcome == BattleOutcome.TotalDefeat) { return; } foreach (BattlePlayerResult playerResult in result.Players) { RunPlayerState state = _players.Find(player => player.CharacterId == playerResult.CharacterId); if (state != null) { state.CurrentHp = playerResult.CurrentHp; state.CurrentMp = playerResult.CurrentMp; } } } private void ResetRunState() { _players.Clear(); _elapsedMs = 0; _timerPaused = false; _explorationPaused = false; _battleActive = false; _runtime = null; _onCompleted = null; StopAutoAdvance(); _stepListener = null; } } }