using System.Collections.Generic; using SepCore.CustomComponent; using SepCore.Definition; namespace SepCore.Battle { /// /// 当前战斗的事实唯一来源,仅战斗期间存在。 /// 调度、指令校验、效果、状态和敌人决策共享同一实例,不复制单位、轮次和状态集合; /// 内部不为函数调用建立请求/响应 DTO,玩家与敌人的行动都走同一条执行管线并产生同一组 BattleEvent。 /// M1 只支持 1 名玩家与 1 名敌人的普通攻击闭环。 /// internal sealed class BattleRuntime { /// /// 触发本场战斗的遭遇 ID,用于结果回写关联地图敌人。 /// public readonly int EncounterId; /// /// 本场全部单位,创建顺序即稳定显示顺序(玩家在前)。 /// public readonly BattleUnit[] Units; /// /// 是否为本场先制战斗(只影响第一轮;M1 未启用)。 /// public readonly bool IsPreemptive; /// /// 当前轮次,从 1 开始。 /// public int RoundNumber = 1; /// /// 当前行动者;无行动者或战斗完成时为 0。 /// public int CurrentActorUnitId; /// /// 本轮已获得行动机会的单位 ID。 /// public readonly HashSet ActedUnitIds = new HashSet(); /// /// 本次推进产生的待展示行动记录,推进结束时被 BattleStep 取走。 /// public readonly List PendingEvents = new List(); /// /// 战斗结束后的结果;非 null 即表示战斗已完成。 /// public BattleResult Result; private readonly IRunRandomSource _random; private readonly IBattleConfigProvider _config; /// /// 是否已完成。 /// public bool IsCompleted => Result != null; /// /// 当前行动者单位;无行动者时返回 null。 /// public BattleUnit CurrentActor => GetUnit(CurrentActorUnitId); private BattleRuntime(int encounterId, BattleUnit[] units, bool isPreemptive, IRunRandomSource random, IBattleConfigProvider config) { EncounterId = encounterId; Units = units; IsPreemptive = isPreemptive; _random = random; _config = config; } /// /// 从遭遇、单局玩家状态、配置和本局随机源创建唯一战斗运行时。 /// 任一必要输入缺失或配置缺失时返回 null,表示启动失败且不产生任何副作用。 /// public static BattleRuntime Create(BattleEncounter encounter, IReadOnlyList players, IBattleConfigProvider config, IRunRandomSource random) { if (encounter == null || players == null || players.Count == 0 || config == null || random == null) { return null; } EnemyPartyConfig party = config.GetEnemyParty(encounter.EnemyPartyConfigId); if (party == null || party.EnemyIds == null || party.EnemyIds.Count == 0) { return null; } List units = new List(players.Count + party.EnemyIds.Count); int nextId = 1; foreach (RunPlayerState state in players) { if (state == null) { return null; } BattleUnit unit = new BattleUnit(nextId++, BattleFaction.Player, state.CharacterId, state.PartyOrder, state.CurrentHp, state.MaxHp, state.CurrentMp, state.MaxMp, state.Atk, state.Mat, state.Speed); if (state.AttackActionId != 0) { unit.ActionIds.Add(state.AttackActionId); } if (state.SkillActionId != 0) { unit.ActionIds.Add(state.SkillActionId); } units.Add(unit); } int enemyOrder = 1; foreach (int enemyId in party.EnemyIds) { EnemyConfig enemy = config.GetEnemy(enemyId); if (enemy == null) { return null; } BattleUnit unit = new BattleUnit(nextId++, BattleFaction.Enemy, enemy.Id, enemyOrder++, enemy.MaxHp, enemy.MaxHp, enemy.MaxMp, enemy.MaxMp, enemy.Atk, enemy.Mat, enemy.Speed); if (enemy.ActionIds != null) { unit.ActionIds.AddRange(enemy.ActionIds); } units.Add(unit); } BattleRuntime runtime = new BattleRuntime(encounter.EncounterId, units.ToArray(), encounter.IsPreemptive, random, config); runtime.SelectNextActor(); return runtime; } /// /// 按运行时 ID 获取单位;不存在时返回 null。 /// public BattleUnit GetUnit(int unitId) { foreach (BattleUnit unit in Units) { if (unit.UnitId == unitId) { return unit; } } return null; } /// /// 单位是否仍在战斗中(未阵亡且未逃跑)。 /// public static bool IsActive(BattleUnit unit) { return unit != null && !unit.IsDefeated && !unit.IsEscaped; } /// /// 校验并执行当前玩家指令(单次行动)。 /// 非法指令不消耗 MP、行动机会或随机数;玩家行动后仅选择下一个行动者, /// 不自动推进敌人回合——间歇节奏由组件在外部编排。 /// public BattleStep SubmitCommand(BattleCommand command) { if (!ValidatePlayerCommand(command)) { return new BattleStep(new BattleEvent[0], BuildViewState(), null); } PendingEvents.Clear(); BattleUnit actor = GetUnit(command.ActorUnitId); ExecuteAction(actor, command.CommandType, command.ActionConfigId, command.TargetUnitIds); MarkActed(command.ActorUnitId); CheckBattleEnd(); if (!IsCompleted) { SelectNextActor(); } return new BattleStep(DrainEvents(), BuildViewState(), Result); } /// /// 推进当前敌人行动者的自动回合(单次行动)。 /// 当前行动者不是敌人、没有行动者或战斗已完成时返回无变化步骤; /// 与玩家指令共用同一执行管线并产生同一组 BattleEvent。 /// public BattleStep AdvanceEnemyTurn() { BattleUnit actor = CurrentActor; if (actor == null || actor.Faction != BattleFaction.Enemy || IsCompleted) { return new BattleStep(new BattleEvent[0], BuildViewState(), null); } PendingEvents.Clear(); ExecuteEnemyTurn(actor); MarkActed(actor.UnitId); CheckBattleEnd(); if (!IsCompleted) { SelectNextActor(); } return new BattleStep(DrainEvents(), BuildViewState(), Result); } /// /// 构建当前只读视图。 /// public BattleViewState BuildViewState() { List unitViews = new List(Units.Length); foreach (BattleUnit unit in Units) { List statusViews = new List(unit.Statuses.Count); foreach (BattleStatus status in unit.Statuses) { statusViews.Add(new BattleStatusView(status.Type, status.RemainingRounds)); } unitViews.Add(new BattleUnitView(unit.UnitId, unit.Faction, unit.ConfigId, unit.PartyOrder, unit.CurrentHp, unit.MaxHp, unit.CurrentMp, unit.MaxMp, unit.Speed, unit.IsDefeated, unit.IsEscaped, statusViews)); } List remainingOrder = new List(); if (!IsCompleted) { foreach (BattleUnit unit in Units) { if (IsActive(unit) && !ActedUnitIds.Contains(unit.UnitId)) { remainingOrder.Add(unit.UnitId); } } } List availableActions = new List(); BattleUnit actor = CurrentActor; if (actor != null && actor.Faction == BattleFaction.Player) { availableActions.AddRange(actor.ActionIds); } return new BattleViewState(RoundNumber, CurrentActorUnitId, unitViews, remainingOrder, availableActions); } private bool ValidatePlayerCommand(BattleCommand command) { if (command == null || IsCompleted) { return false; } if (command.ActorUnitId != CurrentActorUnitId) { return false; } BattleUnit actor = GetUnit(command.ActorUnitId); if (actor == null || actor.Faction != BattleFaction.Player) { return false; } if (command.CommandType != BattleActionType.Attack) { return false; } BattleActionConfig action = _config.GetAction(command.ActionConfigId); if (action == null || action.ActionType != BattleActionType.Attack) { return false; } if (actor.CurrentMp < action.MpCost) { return false; } return TryResolvePlayerTargets(command, actor, action); } private bool TryResolvePlayerTargets(BattleCommand command, BattleUnit actor, BattleActionConfig action) { if (action.TargetType != BattleTargetType.SingleEnemy) { return false; } if (command.TargetUnitIds == null || command.TargetUnitIds.Count != 1) { return false; } BattleUnit target = GetUnit(command.TargetUnitIds[0]); return target != null && target.Faction != actor.Faction && IsActive(target); } private void ExecuteAction(BattleUnit actor, BattleActionType commandType, int actionConfigId, IReadOnlyList targetUnitIds) { BattleActionConfig action = _config.GetAction(actionConfigId); if (action == null) { return; } if (action.MpCost > 0 && actor.CurrentMp >= action.MpCost) { actor.CurrentMp -= action.MpCost; } foreach (int targetUnitId in targetUnitIds) { BattleUnit target = GetUnit(targetUnitId); if (target == null) { continue; } foreach (BattleEffect effect in action.Effects) { ApplyEffect(actor, target, commandType, actionConfigId, effect); } } } private void ApplyEffect(BattleUnit actor, BattleUnit target, BattleActionType commandType, int actionConfigId, BattleEffect effect) { int sourceValue = GetStatValue(actor, effect.SourceStat); int change = effect.FlatValue + sourceValue * effect.SourceScalePermille / 1000; int beforeHp = target.CurrentHp; int beforeMp = target.CurrentMp; switch (effect.TargetStat) { case BattleStatType.HP: target.CurrentHp = Clamp(target.CurrentHp + change, 0, target.MaxHp); if (target.CurrentHp <= 0) { target.CurrentHp = 0; target.IsDefeated = true; } break; case BattleStatType.MP: target.CurrentMp = Clamp(target.CurrentMp + change, 0, target.MaxMp); break; default: // M1 不处理速度/上限等目标属性修改与状态施加 break; } PendingEvents.Add(new BattleEvent(actor.UnitId, commandType, actionConfigId, target.UnitId, beforeHp, target.CurrentHp, beforeMp, target.CurrentMp, BattleStatusType.None, 0)); } private static int GetStatValue(BattleUnit unit, BattleStatType stat) { switch (stat) { case BattleStatType.HP: return unit.CurrentHp; case BattleStatType.MP: return unit.CurrentMp; case BattleStatType.MaxHP: return unit.MaxHp; case BattleStatType.MaxMP: return unit.MaxMp; case BattleStatType.ATK: return unit.Atk; case BattleStatType.MAT: return unit.Mat; case BattleStatType.Speed: return unit.Speed; default: return 0; } } private static int Clamp(int value, int min, int max) { return value < min ? min : (value > max ? max : value); } private void MarkActed(int unitId) { ActedUnitIds.Add(unitId); } /// /// 选择本轮下一个未行动的活跃单位;全部行动过则进入下一轮。 /// M1 按单位创建顺序(玩家在前)轮流,每轮每个单位最多行动一次; /// M2 起由完整调度规则(速度、阵营、队伍顺序、先制)替换。 /// private void SelectNextActor() { if (IsCompleted) { CurrentActorUnitId = 0; return; } BattleUnit next = FindNextActor(); if (next == null) { RoundNumber++; ActedUnitIds.Clear(); next = FindNextActor(); } CurrentActorUnitId = next != null ? next.UnitId : 0; } private BattleUnit FindNextActor() { foreach (BattleUnit unit in Units) { if (IsActive(unit) && !ActedUnitIds.Contains(unit.UnitId)) { return unit; } } return null; } /// /// 敌人随机决策并执行:从 MP 足够的行动中等概率选择,单体行动从合法目标中等概率选择; /// 没有可执行行动或合法目标时跳过本次机会。 /// private void ExecuteEnemyTurn(BattleUnit enemy) { List usableActions = new List(); foreach (int actionId in enemy.ActionIds) { BattleActionConfig action = _config.GetAction(actionId); if (action != null && enemy.CurrentMp >= action.MpCost) { usableActions.Add(actionId); } } if (usableActions.Count == 0) { return; } int chosenActionId = PickOne(usableActions); BattleActionConfig chosen = _config.GetAction(chosenActionId); List targets = new List(); foreach (BattleUnit unit in Units) { if (unit.Faction != enemy.Faction && IsActive(unit)) { targets.Add(unit.UnitId); } } if (targets.Count == 0) { return; } int targetUnitId = PickOne(targets); ExecuteAction(enemy, chosen.ActionType, chosenActionId, new[] { targetUnitId }); } /// /// 等概率选一;只有一项时直接返回,不消费随机数。 /// private int PickOne(IReadOnlyList candidates) { if (candidates.Count == 1) { return candidates[0]; } return candidates[_random.NextInt(0, candidates.Count)]; } private void CheckBattleEnd() { if (Result != null) { return; } bool allEnemiesDefeated = true; bool allPlayersDefeated = true; foreach (BattleUnit unit in Units) { if (IsActive(unit)) { if (unit.Faction == BattleFaction.Enemy) { allEnemiesDefeated = false; } else { allPlayersDefeated = false; } } } BattleOutcome outcome; if (allEnemiesDefeated) { outcome = BattleOutcome.Victory; } else if (allPlayersDefeated) { outcome = BattleOutcome.TotalDefeat; } else { return; } List players = new List(); foreach (BattleUnit unit in Units) { if (unit.Faction == BattleFaction.Player) { players.Add(new BattlePlayerResult(unit.ConfigId, unit.CurrentHp, unit.CurrentMp, unit.IsDefeated, unit.IsEscaped)); } } Result = new BattleResult(EncounterId, outcome, players); CurrentActorUnitId = 0; } private List DrainEvents() { List drained = new List(PendingEvents); PendingEvents.Clear(); return drained; } } }