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; /// /// AI的核心决策方法,根据当前游戏状态为角色选择最佳行动 /// /// 当前行动的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) { // 动态调整概率 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 }; // 候选决策 List candidateDecisions = []; // 遍历所有可能的移动目标格子 (包括起始格子本身) foreach (Grid potentialMoveGrid in allPossibleMoveGrids) { // 计算移动到这个格子的代价(曼哈顿距离) int moveDistance = GameMap.CalculateManhattanDistance(startGrid, potentialMoveGrid); double movePenalty = moveDistance * 0.5; // 每移动一步扣0.5分 if (pNormalAttack > 0) { if (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()]; List normalAttackReachableTeammates = [.. allTeammatesInGame.Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c)).Distinct()]; if (normalAttackReachableEnemys.Count > 0) { // 将筛选后的目标列表传递给 SelectTargets List targets = SelectTargets(character, character.NormalAttack, normalAttackReachableEnemys, normalAttackReachableTeammates); if (targets.Count > 0) { double currentScore = EvaluateNormalAttack(character, targets) - movePenalty; double probabilityWeight = 1.0 + (normalizedPNormalAttack * 0.3); candidateDecisions.Add(new AIDecision { ActionType = CharacterActionType.NormalAttack, TargetMoveGrid = potentialMoveGrid, SkillToUse = character.NormalAttack, Targets = targets, Score = currentScore, ProbabilityWeight = probabilityWeight }); } } } } if (pCastSkill > 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 && nonDirDecision.Score > bestDecision.Score) { bestDecision = 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) { // 将筛选后的目标列表传递给 SelectTargets 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); candidateDecisions.Add(new AIDecision { ActionType = CharacterActionType.PreCastSkill, TargetMoveGrid = potentialMoveGrid, SkillToUse = skill, Targets = targets, Score = currentScore, ProbabilityWeight = probabilityWeight }); } } } } } } if (pUseItem > 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 && nonDirDecision.Score > bestDecision.Score) { bestDecision = 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) { // 将筛选后的目标列表传递给 SelectTargets 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); candidateDecisions.Add(new AIDecision { ActionType = CharacterActionType.UseItem, TargetMoveGrid = potentialMoveGrid, ItemToUse = item, SkillToUse = itemSkill, Targets = targetsForItem, Score = currentScore, ProbabilityWeight = probabilityWeight }); } } } } } } // 如果从该格子没有更好的行动,但移动本身有价值 // 只有当当前最佳决策是“结束回合”或分数很低时,才考虑纯粹的移动 if (potentialMoveGrid != startGrid && bestDecision.Score < 0) // 如果当前最佳决策是负分(即什么都不做) { 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; } } } candidateDecisions.Add(new AIDecision { ActionType = CharacterActionType.Move, TargetMoveGrid = potentialMoveGrid, Targets = [], Score = pureMoveScore, IsPureMove = true }); } // 偏好类型额外加分 if (preferredAction.HasValue) { foreach (var decision in candidateDecisions) { if (decision.ActionType == preferredAction.Value) { decision.Score *= 1.2; } } } // 最终决策 bestDecision = candidateDecisions.OrderByDescending(d => d.Score * d.ProbabilityWeight).FirstOrDefault() ?? bestDecision; } return bestDecision; } // --- 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; } 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 -= cost * 5; //score -= skill.RealCD * 2; //score -= skill.HardnessTime * 2; 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; return new AIDecision { ActionType = CharacterActionType.PreCastSkill, TargetMoveGrid = moveGrid, SkillToUse = skill, Targets = [], TargetGrids = bestTargetGrids, Score = finalScore }; } /// /// 评估物品的价值 /// /// /// /// /// /// private static double EvaluateItem(Character character, Item item, List targets, double cost) { double score = Random.Shared.Next(1000); return score; } /// /// 辅助函数:计算单个目标在某个技能下的价值 /// /// /// /// private static double CalculateTargetValue(Character target, ISkill skill) { double value = Random.Shared.Next(1000); return value; } } }