using System.Collections.Concurrent; using Milimoe.FunGame.Core.Entity; using Milimoe.FunGame.Core.Interface.Entity; using Milimoe.FunGame.Core.Library.Common.Addon; using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Model; namespace Milimoe.FunGame.Core.Controller { public class AIController(GamingQueue queue, GameMap map) { private readonly GamingQueue _queue = queue; private readonly GameMap _map = map; public delegate double EvaluateSkillDelegate(Character character, Skill skill, List targets, double cost); public delegate double EvaluateNormalAttackDelegate(Character character, NormalAttack normalAttack, List targets); public delegate double EvaluateNonDirectionalSkillDelegate(Character character, Skill skill, Grid moveGrid, List castableGrids, List allEnemys, List allTeammates, double cost); public delegate double EvaluateItemDelegate(Character character, Item item, List targets, double cost); public delegate double CalculateTargetValueDelegate(Character character, ISkill skill); public static event EvaluateSkillDelegate? EvaluateSkillEvent; public static event EvaluateNormalAttackDelegate? EvaluateNormalAttackEvent; public static event EvaluateNonDirectionalSkillDelegate? EvaluateNonDirectionalSkillEvent; public static event EvaluateItemDelegate? EvaluateItemEvent; public static event CalculateTargetValueDelegate? CalculateTargetValueEvent; /// /// 核心决策方法:外部同步,内部异步并行计算所有独立决策的分数 /// /// 当前行动的AI角色 /// 角色的决策点 /// 角色的起始格子 /// 从起始格子可达的所有移动格子(包括起始格子本身) /// 角色所有可用的技能(已过滤CD和EP/MP) /// 角色所有可用的物品(已过滤CD和EP/MP) /// 场上所有敌人 /// 场上所有队友 /// 场上能够选取的敌人 /// 场上能够选取的队友 /// 使用物品的概率 /// 释放技能的概率 /// 普通攻击的概率 /// 包含最佳行动的AIDecision对象 public AIDecision DecideAIAction(Character character, DecisionPoints dp, Grid startGrid, List allPossibleMoveGrids, List availableSkills, List availableItems, List allEnemysInGame, List allTeammatesInGame, List selectableEnemys, List selectableTeammates, double pUseItem, double pCastSkill, double pNormalAttack) { // 控制最大并发数 int maxConcurrency = Math.Max(1, Environment.ProcessorCount / 2); SemaphoreSlim semaphore = new(maxConcurrency); // 动态调整概率 double dynamicPUseItem = pUseItem; double dynamicPCastSkill = pCastSkill; double dynamicPNormalAttack = pNormalAttack; AdjustProbabilitiesBasedOnContext(ref dynamicPUseItem, ref dynamicPCastSkill, ref dynamicPNormalAttack, character, allEnemysInGame, allTeammatesInGame); // 归一化概率 double[] normalizedProbs = NormalizeProbabilities(dynamicPUseItem, dynamicPCastSkill, dynamicPNormalAttack); double normalizedPUseItem = normalizedProbs[0]; double normalizedPCastSkill = normalizedProbs[1]; double normalizedPNormalAttack = normalizedProbs[2]; // 获取偏好行动类型 CharacterActionType? preferredAction = GetPreferredActionType(pUseItem, pCastSkill, pNormalAttack); // 初始化一个默认的“结束回合”决策作为基准 AIDecision bestDecision = new() { ActionType = CharacterActionType.EndTurn, TargetMoveGrid = startGrid, Targets = [], Score = -1000.0 }; // 候选决策 ConcurrentBag candidateDecisions = []; // 封装单个移动格子的决策计算逻辑为异步任务 List decisionTasks = []; foreach (Grid potentialMoveGrid in allPossibleMoveGrids) { // 捕获循环变量(避免闭包陷阱) Grid currentMoveGrid = potentialMoveGrid; Task task = Task.Run(async () => { await semaphore.WaitAsync(); try { CalculateDecisionForGrid( character, dp, startGrid, currentMoveGrid, availableSkills, availableItems, allEnemysInGame, allTeammatesInGame, selectableEnemys, selectableTeammates, normalizedPUseItem, normalizedPCastSkill, normalizedPNormalAttack, preferredAction, candidateDecisions ); } catch (Exception ex) { // 单个格子的计算异常不影响整体,记录日志 Console.WriteLine($"计算格子[{currentMoveGrid.X},{currentMoveGrid.Y}]决策失败:{ex.Message}"); } finally { semaphore.Release(); } }); decisionTasks.Add(task); } // 等待所有异步任务完成 Task.WaitAll(decisionTasks); // 从所有候选决策中选出最高分的(保留原有评分权重逻辑) if (!candidateDecisions.IsEmpty) { bestDecision = candidateDecisions .OrderByDescending(d => d.Score * d.ProbabilityWeight) .FirstOrDefault() ?? bestDecision; } return bestDecision; } /// /// 异步执行的核心:计算单个移动格子的所有可能决策,并添加到线程安全容器 /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// private void CalculateDecisionForGrid( Character character, DecisionPoints dp, Grid startGrid, Grid potentialMoveGrid, List availableSkills, List availableItems, List allEnemysInGame, List allTeammatesInGame, List selectableEnemys, List selectableTeammates, double normalizedPUseItem, double normalizedPCastSkill, double normalizedPNormalAttack, CharacterActionType? preferredAction, ConcurrentBag candidateDecisions) { // 计算移动惩罚 int moveDistance = GameMap.CalculateManhattanDistance(startGrid, potentialMoveGrid); double movePenalty = moveDistance * 0.5; // 计算普通攻击决策 if (normalizedPNormalAttack > 0 && CanCharacterNormalAttack(character, dp)) { List normalAttackReachableGrids = _map.GetGridsByRange(potentialMoveGrid, character.ATR, true); List normalAttackReachableEnemys = [.. allEnemysInGame.Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)).Distinct()]; if (normalAttackReachableEnemys.Count > 0) { List targets = SelectTargets(character, character.NormalAttack, normalAttackReachableEnemys, []); if (targets.Count > 0) { double currentScore = EvaluateNormalAttack(character, targets) - movePenalty; double probabilityWeight = 1.0 + (normalizedPNormalAttack * 0.3); AIDecision attackDecision = new() { ActionType = CharacterActionType.NormalAttack, TargetMoveGrid = potentialMoveGrid, SkillToUse = character.NormalAttack, Targets = targets, Score = currentScore, ProbabilityWeight = probabilityWeight }; // 偏好类型加分 if (preferredAction.HasValue && attackDecision.ActionType == preferredAction.Value) { attackDecision.Score *= 1.2; } candidateDecisions.Add(attackDecision); } } } // 计算技能释放决策 if (normalizedPCastSkill > 0) { foreach (Skill skill in availableSkills) { if (CanCharacterUseSkill(character, skill, dp) && _queue.CheckCanCast(character, skill, out double cost)) { List skillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true); if (skill.IsNonDirectional) { AIDecision? nonDirDecision = EvaluateNonDirectionalSkill( character, skill, potentialMoveGrid, skillReachableGrids, allEnemysInGame, allTeammatesInGame, cost); if (nonDirDecision != null) { // 偏好类型加分 if (preferredAction.HasValue && nonDirDecision.ActionType == preferredAction.Value) { nonDirDecision.Score *= 1.2; } candidateDecisions.Add(nonDirDecision); } } else { List skillReachableEnemys = [.. allEnemysInGame.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)).Distinct()]; List skillReachableTeammates = [.. allTeammatesInGame.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c)).Distinct()]; if (skillReachableEnemys.Count > 0 || skillReachableTeammates.Count > 0) { List targets = SelectTargets(character, skill, skillReachableEnemys, skillReachableTeammates); if (targets.Count > 0) { double currentScore = EvaluateSkill(character, skill, targets, cost) - movePenalty; double probabilityWeight = 1.0 + (normalizedPCastSkill * 0.3); AIDecision skillDecision = new() { ActionType = CharacterActionType.PreCastSkill, TargetMoveGrid = potentialMoveGrid, SkillToUse = skill, Targets = targets, Score = currentScore, ProbabilityWeight = probabilityWeight }; // 偏好类型加分 if (preferredAction.HasValue && skillDecision.ActionType == preferredAction.Value) { skillDecision.Score *= 1.2; } candidateDecisions.Add(skillDecision); } } } } } } // 计算物品使用决策 if (normalizedPUseItem > 0) { foreach (Item item in availableItems) { if (item.Skills.Active != null && CanCharacterUseItem(character, item, dp) && _queue.CheckCanCast(character, item.Skills.Active, out double cost)) { Skill itemSkill = item.Skills.Active; List itemSkillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, itemSkill.CastRange, true); if (itemSkill.IsNonDirectional) { AIDecision? nonDirDecision = EvaluateNonDirectionalSkill( character, itemSkill, potentialMoveGrid, itemSkillReachableGrids, allEnemysInGame, allTeammatesInGame, cost); if (nonDirDecision != null) { // 偏好类型加分 if (preferredAction.HasValue && nonDirDecision.ActionType == preferredAction.Value) { nonDirDecision.Score *= 1.2; } candidateDecisions.Add(nonDirDecision); } } else { List itemSkillReachableEnemys = [.. allEnemysInGame.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)).Distinct()]; List itemSkillReachableTeammates = [.. allTeammatesInGame.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c)).Distinct()]; if (itemSkillReachableEnemys.Count > 0 || itemSkillReachableTeammates.Count > 0) { List targetsForItem = SelectTargets(character, itemSkill, itemSkillReachableEnemys, itemSkillReachableTeammates); if (targetsForItem.Count > 0) { double currentScore = EvaluateItem(character, item, targetsForItem, cost) - movePenalty; double probabilityWeight = 1.0 + (normalizedPUseItem * 0.3); AIDecision itemDecision = new() { ActionType = CharacterActionType.UseItem, TargetMoveGrid = potentialMoveGrid, ItemToUse = item, SkillToUse = itemSkill, Targets = targetsForItem, Score = currentScore, ProbabilityWeight = probabilityWeight }; // 偏好类型加分 if (preferredAction.HasValue && itemDecision.ActionType == preferredAction.Value) { itemDecision.Score *= 1.2; } candidateDecisions.Add(itemDecision); } } } } } } // 计算纯移动决策 if (potentialMoveGrid != startGrid) { double pureMoveScore = -movePenalty; List tempAttackGridsForPureMove = _map.GetGridsByRange(potentialMoveGrid, character.ATR, true); List tempCastGridsForPureMove = []; foreach (Skill skill in availableSkills) { tempCastGridsForPureMove.AddRange(_map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true)); } foreach (Item item in availableItems) { if (item.Skills.Active != null) { tempCastGridsForPureMove.AddRange(_map.GetGridsByRange(potentialMoveGrid, item.Skills.Active.CastRange, true)); } } List tempAllReachableGridsForPureMove = [.. tempAttackGridsForPureMove.Union(tempCastGridsForPureMove).Distinct()]; List tempCurrentReachableEnemysForPureMove = [.. allEnemysInGame.Where(c => tempAllReachableGridsForPureMove.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)).Distinct()]; if (tempCurrentReachableEnemysForPureMove.Count == 0 && allEnemysInGame.Count > 0) { Character? target = allEnemysInGame.OrderBy(e => GameMap.CalculateManhattanDistance(potentialMoveGrid, _map.GetCharacterCurrentGrid(e) ?? Grid.Empty)).FirstOrDefault(); if (target != null) { Grid? nearestEnemyGrid = _map.GetCharacterCurrentGrid(target); if (nearestEnemyGrid != null) { pureMoveScore += (10 - GameMap.CalculateManhattanDistance(potentialMoveGrid, nearestEnemyGrid)) * 0.1; } } } AIDecision moveDecision = new() { ActionType = CharacterActionType.Move, TargetMoveGrid = potentialMoveGrid, Targets = [], Score = pureMoveScore, IsPureMove = true, ProbabilityWeight = 1.0 // 纯移动无概率权重 }; // 偏好类型加分(如果移动是偏好类型) if (preferredAction.HasValue && moveDecision.ActionType == preferredAction.Value) { moveDecision.Score *= 1.2; } candidateDecisions.Add(moveDecision); } } // --- AI 决策辅助方法 --- // 获取偏好行动类型 private static CharacterActionType? GetPreferredActionType(double pItem, double pSkill, double pAttack) { // 找出最高概率的行动类型 Dictionary probabilities = new() { { CharacterActionType.UseItem, pItem }, { CharacterActionType.PreCastSkill, pSkill }, { CharacterActionType.NormalAttack, pAttack } }; double maxProb = probabilities.Values.Max(); if (maxProb > 0) { CharacterActionType preferredType = probabilities.FirstOrDefault(kvp => kvp.Value == maxProb).Key; // 如果最高概率超过阈值,优先考虑该类型 if (maxProb > 0.7) { return preferredType; } } return null; } // 动态调整概率 private static void AdjustProbabilitiesBasedOnContext(ref double pUseItem, ref double pCastSkill, ref double pNormalAttack, Character character, List allEnemysInGame, List allTeammatesInGame) { // 基于角色状态调整 if (character.HP / character.MaxHP < 0.3 || allTeammatesInGame.Any(c => c.HP / c.MaxHP < 0.3)) { // 低生命值时增加使用物品概率 if (pUseItem > 0) pUseItem *= 1.5; if (pCastSkill > 0) pCastSkill *= 0.7; } // 基于敌人数量调整 int enemyCount = allEnemysInGame.Count; if (enemyCount > 3) { // 敌人多时倾向于范围技能 if (pCastSkill > 0) pCastSkill *= 1.3; } else if (enemyCount == 1) { // 单个敌人时倾向于普通攻击 if (pNormalAttack > 0) pNormalAttack *= 1.2; } if (pUseItem > 0) pUseItem = Math.Max(0, pUseItem); if (pCastSkill > 0) pCastSkill = Math.Max(0, pCastSkill); if (pNormalAttack > 0) pNormalAttack = Math.Max(0, pNormalAttack); } // / 归一化概率分布 private static double[] NormalizeProbabilities(double pUseItem, double pCastSkill, double pNormalAttack) { if (pUseItem <= 0 && pCastSkill <= 0 && pNormalAttack <= 0) { return [0, 0, 0]; } double sum = pUseItem + pCastSkill + pNormalAttack; if (sum <= 0) return [0.33, 0.33, 0.34]; return [ pUseItem / sum, pCastSkill / sum, pNormalAttack / sum ]; } // 检查角色是否能进行普通攻击(基于状态) private static bool CanCharacterNormalAttack(Character character, DecisionPoints dp) { return dp.CheckActionTypeQuota(CharacterActionType.NormalAttack) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostNormalAttack && character.CharacterState != CharacterState.NotActionable && character.CharacterState != CharacterState.ActionRestricted && character.CharacterState != CharacterState.BattleRestricted && character.CharacterState != CharacterState.AttackRestricted; } // 检查角色是否能使用某个技能(基于状态) private static bool CanCharacterUseSkill(Character character, Skill skill, DecisionPoints dp) { return ( (skill.SkillType == SkillType.Magic && dp.CheckActionTypeQuota(CharacterActionType.PreCastSkill) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostMagic) || (skill.SkillType == SkillType.Skill && dp.CheckActionTypeQuota(CharacterActionType.CastSkill) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostSkill) || (skill.SkillType == SkillType.SuperSkill && dp.CheckActionTypeQuota(CharacterActionType.CastSuperSkill) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostSuperSkill) ) && character.CharacterState != CharacterState.NotActionable && character.CharacterState != CharacterState.ActionRestricted && character.CharacterState != CharacterState.BattleRestricted && character.CharacterState != CharacterState.SkillRestricted; } // 检查角色是否能使用某个物品(基于状态) private static bool CanCharacterUseItem(Character character, Item item, DecisionPoints dp) { return dp.CheckActionTypeQuota(CharacterActionType.UseItem) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostItem && character.CharacterState != CharacterState.NotActionable && (character.CharacterState != CharacterState.ActionRestricted || item.ItemType == ItemType.Consumable) && character.CharacterState != CharacterState.BattleRestricted; } // 选择技能的最佳目标 private static List SelectTargets(Character character, ISkill skill, List enemys, List teammates) { List targets = skill.GetSelectableTargets(character, enemys, teammates); int count = skill.RealCanSelectTargetCount(enemys, teammates); return [.. targets.OrderBy(o => Random.Shared.Next()).Take(count)]; } // 评估普通攻击的价值 private static double EvaluateNormalAttack(Character character, List targets) { double score = 0; foreach (Character target in targets) { double damage = character.NormalAttack.Damage * (1 - target.PDR); score += damage; if (target.HP <= damage) score += 100; } score += EvaluateNormalAttackEvent?.Invoke(character, character.NormalAttack, targets) ?? 0; return score; } // 评估技能的价值 private static double EvaluateSkill(Character character, Skill skill, List targets, double cost) { double score = 0; score += targets.Sum(t => CalculateTargetValue(t, skill)); score += EvaluateSkillEvent?.Invoke(character, skill, targets, cost) ?? 0; return score; } // 非指向性技能的评估 private AIDecision? EvaluateNonDirectionalSkill(Character character, Skill skill, Grid moveGrid, List castableGrids, List allEnemys, List allTeammates, double cost) { double bestSkillScore = double.NegativeInfinity; List bestTargetGrids = []; // 枚举所有可施放的格子作为潜在中心 foreach (Grid centerGrid in castableGrids) { // 计算该中心格子下的实际影响范围格子 List effectGrids = skill.SelectNonDirectionalTargets(character, centerGrid, skill.SelectIncludeCharacterGrid); // 计算实际影响的角色 List affected = skill.SelectTargetsByRange(character, allEnemys, allTeammates, [], effectGrids); if (affected.Count == 0) continue; // 评估这些影响目标的价值 double skillScore = affected.Sum(t => CalculateTargetValue(t, skill)); if (skillScore > bestSkillScore) { bestSkillScore = skillScore; bestTargetGrids = effectGrids; } } if (bestSkillScore == double.NegativeInfinity) return null; // 无有效格子 double movePenalty = GameMap.CalculateManhattanDistance(_map.GetCharacterCurrentGrid(character)!, moveGrid) * 0.5; double finalScore = bestSkillScore - movePenalty; finalScore += EvaluateNonDirectionalSkillEvent?.Invoke(character, skill, moveGrid, castableGrids, allEnemys, allTeammates, cost) ?? 0; return new AIDecision { ActionType = CharacterActionType.PreCastSkill, TargetMoveGrid = moveGrid, SkillToUse = skill, Targets = [], TargetGrids = bestTargetGrids, Score = finalScore, ProbabilityWeight = 1.0 // 非指向性技能默认权重 }; } // 评估物品的价值 private static double EvaluateItem(Character character, Item item, List targets, double cost) { double score = Random.Shared.Next(1000); score += EvaluateItemEvent?.Invoke(character, item, targets, cost) ?? 0; return score; } // 辅助函数:计算单个目标在某个技能下的价值 private static double CalculateTargetValue(Character target, ISkill skill) { double value = Random.Shared.Next(1000); value += CalculateTargetValueEvent?.Invoke(target, skill) ?? 0; return value; } } }