mirror of
https://github.com/project-redbud/FunGame-Core.git
synced 2026-03-05 22:20:26 +00:00
AIController 使用多线程计算;选择目标前的询问顺序改动
This commit is contained in:
parent
4c01320b7a
commit
c4e29b1f4f
@ -1,4 +1,5 @@
|
||||
using Milimoe.FunGame.Core.Entity;
|
||||
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;
|
||||
@ -11,8 +12,19 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
private readonly GamingQueue _queue = queue;
|
||||
private readonly GameMap _map = map;
|
||||
|
||||
public delegate double EvaluateSkillDelegate(Character character, Skill skill, List<Character> targets, double cost);
|
||||
public delegate double EvaluateNormalAttackDelegate(Character character, NormalAttack normalAttack, List<Character> targets);
|
||||
public delegate double EvaluateNonDirectionalSkillDelegate(Character character, Skill skill, Grid moveGrid, List<Grid> castableGrids, List<Character> allEnemys, List<Character> allTeammates, double cost);
|
||||
public delegate double EvaluateItemDelegate(Character character, Item item, List<Character> 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;
|
||||
|
||||
/// <summary>
|
||||
/// AI的核心决策方法,根据当前游戏状态为角色选择最佳行动
|
||||
/// 核心决策方法:外部同步,内部异步并行计算所有独立决策的分数
|
||||
/// </summary>
|
||||
/// <param name="character">当前行动的AI角色</param>
|
||||
/// <param name="dp">角色的决策点</param>
|
||||
@ -32,6 +44,10 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
List<Skill> availableSkills, List<Item> availableItems, List<Character> allEnemysInGame, List<Character> allTeammatesInGame,
|
||||
List<Character> selectableEnemys, List<Character> 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;
|
||||
@ -57,34 +73,101 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
};
|
||||
|
||||
// 候选决策
|
||||
List<AIDecision> candidateDecisions = [];
|
||||
ConcurrentBag<AIDecision> candidateDecisions = [];
|
||||
|
||||
// 遍历所有可能的移动目标格子 (包括起始格子本身)
|
||||
// 封装单个移动格子的决策计算逻辑为异步任务
|
||||
List<Task> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步执行的核心:计算单个移动格子的所有可能决策,并添加到线程安全容器
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <param name="startGrid"></param>
|
||||
/// <param name="potentialMoveGrid"></param>
|
||||
/// <param name="availableSkills"></param>
|
||||
/// <param name="availableItems"></param>
|
||||
/// <param name="allEnemysInGame"></param>
|
||||
/// <param name="allTeammatesInGame"></param>
|
||||
/// <param name="selectableEnemys"></param>
|
||||
/// <param name="selectableTeammates"></param>
|
||||
/// <param name="normalizedPUseItem"></param>
|
||||
/// <param name="normalizedPCastSkill"></param>
|
||||
/// <param name="normalizedPNormalAttack"></param>
|
||||
/// <param name="preferredAction"></param>
|
||||
/// <param name="candidateDecisions"></param>
|
||||
private void CalculateDecisionForGrid(
|
||||
Character character, DecisionPoints dp, Grid startGrid, Grid potentialMoveGrid,
|
||||
List<Skill> availableSkills, List<Item> availableItems, List<Character> allEnemysInGame, List<Character> allTeammatesInGame,
|
||||
List<Character> selectableEnemys, List<Character> selectableTeammates,
|
||||
double normalizedPUseItem, double normalizedPCastSkill, double normalizedPNormalAttack,
|
||||
CharacterActionType? preferredAction,
|
||||
ConcurrentBag<AIDecision> candidateDecisions)
|
||||
{
|
||||
// 计算移动惩罚
|
||||
int moveDistance = GameMap.CalculateManhattanDistance(startGrid, potentialMoveGrid);
|
||||
double movePenalty = moveDistance * 0.5; // 每移动一步扣0.5分
|
||||
double movePenalty = moveDistance * 0.5;
|
||||
|
||||
if (pNormalAttack > 0)
|
||||
// 计算普通攻击决策
|
||||
if (normalizedPNormalAttack > 0 && CanCharacterNormalAttack(character, dp))
|
||||
{
|
||||
if (CanCharacterNormalAttack(character, dp))
|
||||
{
|
||||
// 计算普通攻击的可达格子
|
||||
List<Grid> normalAttackReachableGrids = _map.GetGridsByRange(potentialMoveGrid, character.ATR, true);
|
||||
|
||||
List<Character> normalAttackReachableEnemys = [.. allEnemysInGame.Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)).Distinct()];
|
||||
List<Character> normalAttackReachableTeammates = [.. allTeammatesInGame.Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c)).Distinct()];
|
||||
List<Character> normalAttackReachableEnemys = [.. allEnemysInGame.Where(c =>
|
||||
normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c)
|
||||
&& !c.IsUnselectable
|
||||
&& selectableEnemys.Contains(c)).Distinct()];
|
||||
|
||||
if (normalAttackReachableEnemys.Count > 0)
|
||||
{
|
||||
// 将筛选后的目标列表传递给 SelectTargets
|
||||
List<Character> targets = SelectTargets(character, character.NormalAttack, normalAttackReachableEnemys, normalAttackReachableTeammates);
|
||||
List<Character> targets = SelectTargets(character, character.NormalAttack, normalAttackReachableEnemys, []);
|
||||
if (targets.Count > 0)
|
||||
{
|
||||
double currentScore = EvaluateNormalAttack(character, targets) - movePenalty;
|
||||
double probabilityWeight = 1.0 + (normalizedPNormalAttack * 0.3);
|
||||
candidateDecisions.Add(new AIDecision
|
||||
AIDecision attackDecision = new()
|
||||
{
|
||||
ActionType = CharacterActionType.NormalAttack,
|
||||
TargetMoveGrid = potentialMoveGrid,
|
||||
@ -92,45 +175,60 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
Targets = targets,
|
||||
Score = currentScore,
|
||||
ProbabilityWeight = probabilityWeight
|
||||
});
|
||||
};
|
||||
// 偏好类型加分
|
||||
if (preferredAction.HasValue && attackDecision.ActionType == preferredAction.Value)
|
||||
{
|
||||
attackDecision.Score *= 1.2;
|
||||
}
|
||||
candidateDecisions.Add(attackDecision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pCastSkill > 0)
|
||||
// 计算技能释放决策
|
||||
if (normalizedPCastSkill > 0)
|
||||
{
|
||||
foreach (Skill skill in availableSkills)
|
||||
{
|
||||
if (CanCharacterUseSkill(character, skill, dp) && _queue.CheckCanCast(character, skill, out double cost))
|
||||
{
|
||||
// 计算当前技能的可达格子
|
||||
List<Grid> skillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true);
|
||||
|
||||
if (skill.IsNonDirectional)
|
||||
{
|
||||
AIDecision? nonDirDecision = EvaluateNonDirectionalSkill(character, skill, potentialMoveGrid, skillReachableGrids, allEnemysInGame, allTeammatesInGame, cost);
|
||||
AIDecision? nonDirDecision = EvaluateNonDirectionalSkill(
|
||||
character, skill, potentialMoveGrid, skillReachableGrids,
|
||||
allEnemysInGame, allTeammatesInGame, cost);
|
||||
|
||||
if (nonDirDecision != null && nonDirDecision.Score > bestDecision.Score)
|
||||
if (nonDirDecision != null)
|
||||
{
|
||||
bestDecision = nonDirDecision;
|
||||
// 偏好类型加分
|
||||
if (preferredAction.HasValue && nonDirDecision.ActionType == preferredAction.Value)
|
||||
{
|
||||
nonDirDecision.Score *= 1.2;
|
||||
}
|
||||
candidateDecisions.Add(nonDirDecision);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Character> skillReachableEnemys = [.. allEnemysInGame.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)).Distinct()];
|
||||
List<Character> skillReachableTeammates = [.. allTeammatesInGame.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c)).Distinct()];
|
||||
List<Character> skillReachableEnemys = [.. allEnemysInGame.Where(c =>
|
||||
skillReachableGrids.SelectMany(g => g.Characters).Contains(c)
|
||||
&& !c.IsUnselectable
|
||||
&& selectableEnemys.Contains(c)).Distinct()];
|
||||
List<Character> skillReachableTeammates = [.. allTeammatesInGame.Where(c =>
|
||||
skillReachableGrids.SelectMany(g => g.Characters).Contains(c)
|
||||
&& selectableTeammates.Contains(c)).Distinct()];
|
||||
|
||||
// 检查是否有可用的目标(敌人或队友,取决于技能类型)
|
||||
if (skillReachableEnemys.Count > 0 || skillReachableTeammates.Count > 0)
|
||||
{
|
||||
// 将筛选后的目标列表传递给 SelectTargets
|
||||
List<Character> 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
|
||||
AIDecision skillDecision = new()
|
||||
{
|
||||
ActionType = CharacterActionType.PreCastSkill,
|
||||
TargetMoveGrid = potentialMoveGrid,
|
||||
@ -138,7 +236,13 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
Targets = targets,
|
||||
Score = currentScore,
|
||||
ProbabilityWeight = probabilityWeight
|
||||
});
|
||||
};
|
||||
// 偏好类型加分
|
||||
if (preferredAction.HasValue && skillDecision.ActionType == preferredAction.Value)
|
||||
{
|
||||
skillDecision.Score *= 1.2;
|
||||
}
|
||||
candidateDecisions.Add(skillDecision);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -146,41 +250,50 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
}
|
||||
}
|
||||
|
||||
if (pUseItem > 0)
|
||||
// 计算物品使用决策
|
||||
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<Grid> itemSkillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, itemSkill.CastRange, true);
|
||||
|
||||
if (itemSkill.IsNonDirectional)
|
||||
{
|
||||
AIDecision? nonDirDecision = EvaluateNonDirectionalSkill(character, itemSkill, potentialMoveGrid, itemSkillReachableGrids, allEnemysInGame, allTeammatesInGame, cost);
|
||||
AIDecision? nonDirDecision = EvaluateNonDirectionalSkill(
|
||||
character, itemSkill, potentialMoveGrid, itemSkillReachableGrids,
|
||||
allEnemysInGame, allTeammatesInGame, cost);
|
||||
|
||||
if (nonDirDecision != null && nonDirDecision.Score > bestDecision.Score)
|
||||
if (nonDirDecision != null)
|
||||
{
|
||||
bestDecision = nonDirDecision;
|
||||
// 偏好类型加分
|
||||
if (preferredAction.HasValue && nonDirDecision.ActionType == preferredAction.Value)
|
||||
{
|
||||
nonDirDecision.Score *= 1.2;
|
||||
}
|
||||
candidateDecisions.Add(nonDirDecision);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Character> itemSkillReachableEnemys = [.. allEnemysInGame.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)).Distinct()];
|
||||
List<Character> itemSkillReachableTeammates = [.. allTeammatesInGame.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c)).Distinct()];
|
||||
List<Character> itemSkillReachableEnemys = [.. allEnemysInGame.Where(c =>
|
||||
itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c)
|
||||
&& !c.IsUnselectable
|
||||
&& selectableEnemys.Contains(c)).Distinct()];
|
||||
List<Character> itemSkillReachableTeammates = [.. allTeammatesInGame.Where(c =>
|
||||
itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c)
|
||||
&& selectableTeammates.Contains(c)).Distinct()];
|
||||
|
||||
// 检查是否有可用的目标
|
||||
if (itemSkillReachableEnemys.Count > 0 || itemSkillReachableTeammates.Count > 0)
|
||||
{
|
||||
// 将筛选后的目标列表传递给 SelectTargets
|
||||
List<Character> 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
|
||||
AIDecision itemDecision = new()
|
||||
{
|
||||
ActionType = CharacterActionType.UseItem,
|
||||
TargetMoveGrid = potentialMoveGrid,
|
||||
@ -189,21 +302,25 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
Targets = targetsForItem,
|
||||
Score = currentScore,
|
||||
ProbabilityWeight = probabilityWeight
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果从该格子没有更好的行动,但移动本身有价值
|
||||
// 只有当当前最佳决策是“结束回合”或分数很低时,才考虑纯粹的移动
|
||||
if (potentialMoveGrid != startGrid && bestDecision.Score < 0) // 如果当前最佳决策是负分(即什么都不做)
|
||||
};
|
||||
// 偏好类型加分
|
||||
if (preferredAction.HasValue && itemDecision.ActionType == preferredAction.Value)
|
||||
{
|
||||
double pureMoveScore = -movePenalty; // 移动本身有代价
|
||||
itemDecision.Score *= 1.2;
|
||||
}
|
||||
candidateDecisions.Add(itemDecision);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 计算纯移动决策
|
||||
if (potentialMoveGrid != startGrid)
|
||||
{
|
||||
double pureMoveScore = -movePenalty;
|
||||
|
||||
// 为纯粹移动逻辑重新计算综合可达敌人列表
|
||||
List<Grid> tempAttackGridsForPureMove = _map.GetGridsByRange(potentialMoveGrid, character.ATR, true);
|
||||
List<Grid> tempCastGridsForPureMove = [];
|
||||
foreach (Skill skill in availableSkills)
|
||||
@ -218,53 +335,44 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
}
|
||||
}
|
||||
List<Grid> tempAllReachableGridsForPureMove = [.. tempAttackGridsForPureMove.Union(tempCastGridsForPureMove).Distinct()];
|
||||
List<Character> tempCurrentReachableEnemysForPureMove = [.. allEnemysInGame.Where(c => tempAllReachableGridsForPureMove.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)).Distinct()];
|
||||
List<Character> tempCurrentReachableEnemysForPureMove = [.. allEnemysInGame.Where(c =>
|
||||
tempAllReachableGridsForPureMove.SelectMany(g => g.Characters).Contains(c)
|
||||
&& !c.IsUnselectable
|
||||
&& selectableEnemys.Contains(c)).Distinct()];
|
||||
|
||||
// 如果当前位置无法攻击任何敌人,但地图上还有敌人,尝试向最近的敌人移动
|
||||
if (tempCurrentReachableEnemysForPureMove.Count == 0 && allEnemysInGame.Count > 0) // 使用新计算的列表
|
||||
if (tempCurrentReachableEnemysForPureMove.Count == 0 && allEnemysInGame.Count > 0)
|
||||
{
|
||||
Character? target = allEnemysInGame.OrderBy(e => GameMap.CalculateManhattanDistance(potentialMoveGrid, _map.GetCharacterCurrentGrid(e) ?? Grid.Empty)).FirstOrDefault();
|
||||
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
|
||||
AIDecision moveDecision = new()
|
||||
{
|
||||
ActionType = CharacterActionType.Move,
|
||||
TargetMoveGrid = potentialMoveGrid,
|
||||
Targets = [],
|
||||
Score = pureMoveScore,
|
||||
IsPureMove = true
|
||||
});
|
||||
}
|
||||
|
||||
// 偏好类型额外加分
|
||||
if (preferredAction.HasValue)
|
||||
IsPureMove = true,
|
||||
ProbabilityWeight = 1.0 // 纯移动无概率权重
|
||||
};
|
||||
// 偏好类型加分(如果移动是偏好类型)
|
||||
if (preferredAction.HasValue && moveDecision.ActionType == preferredAction.Value)
|
||||
{
|
||||
foreach (var decision in candidateDecisions)
|
||||
{
|
||||
if (decision.ActionType == preferredAction.Value)
|
||||
{
|
||||
decision.Score *= 1.2;
|
||||
moveDecision.Score *= 1.2;
|
||||
}
|
||||
candidateDecisions.Add(moveDecision);
|
||||
}
|
||||
}
|
||||
|
||||
// 最终决策
|
||||
bestDecision = candidateDecisions.OrderByDescending(d => d.Score * d.ProbabilityWeight).FirstOrDefault() ?? bestDecision;
|
||||
}
|
||||
|
||||
return bestDecision;
|
||||
}
|
||||
|
||||
// --- AI 决策辅助方法 ---
|
||||
|
||||
// 获取偏好行动类型
|
||||
@ -343,42 +451,39 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
// 检查角色是否能进行普通攻击(基于状态)
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选择技能的最佳目标
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="skill"></param>
|
||||
/// <param name="enemys"></param>
|
||||
/// <param name="teammates"></param>
|
||||
/// <returns></returns>
|
||||
// 选择技能的最佳目标
|
||||
private static List<Character> SelectTargets(Character character, ISkill skill, List<Character> enemys, List<Character> teammates)
|
||||
{
|
||||
List<Character> targets = skill.GetSelectableTargets(character, enemys, teammates);
|
||||
@ -386,12 +491,7 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
return [.. targets.OrderBy(o => Random.Shared.Next()).Take(count)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估普通攻击的价值
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="targets"></param>
|
||||
/// <returns></returns>
|
||||
// 评估普通攻击的价值
|
||||
private static double EvaluateNormalAttack(Character character, List<Character> targets)
|
||||
{
|
||||
double score = 0;
|
||||
@ -401,24 +501,16 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
score += damage;
|
||||
if (target.HP <= damage) score += 100;
|
||||
}
|
||||
score += EvaluateNormalAttackEvent?.Invoke(character, character.NormalAttack, targets) ?? 0;
|
||||
return score;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估技能的价值
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="skill"></param>
|
||||
/// <param name="targets"></param>
|
||||
/// <param name="cost"></param>
|
||||
/// <returns></returns>
|
||||
// 评估技能的价值
|
||||
private static double EvaluateSkill(Character character, Skill skill, List<Character> targets, double cost)
|
||||
{
|
||||
double score = 0;
|
||||
score += targets.Sum(t => CalculateTargetValue(t, skill));
|
||||
//score -= cost * 5;
|
||||
//score -= skill.RealCD * 2;
|
||||
//score -= skill.HardnessTime * 2;
|
||||
score += EvaluateSkillEvent?.Invoke(character, skill, targets, cost) ?? 0;
|
||||
return score;
|
||||
}
|
||||
|
||||
@ -455,6 +547,7 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
|
||||
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
|
||||
{
|
||||
@ -463,33 +556,24 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
SkillToUse = skill,
|
||||
Targets = [],
|
||||
TargetGrids = bestTargetGrids,
|
||||
Score = finalScore
|
||||
Score = finalScore,
|
||||
ProbabilityWeight = 1.0 // 非指向性技能默认权重
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估物品的价值
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="targets"></param>
|
||||
/// <param name="cost"></param>
|
||||
/// <returns></returns>
|
||||
// 评估物品的价值
|
||||
private static double EvaluateItem(Character character, Item item, List<Character> targets, double cost)
|
||||
{
|
||||
double score = Random.Shared.Next(1000);
|
||||
score += EvaluateItemEvent?.Invoke(character, item, targets, cost) ?? 0;
|
||||
return score;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 辅助函数:计算单个目标在某个技能下的价值
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="skill"></param>
|
||||
/// <returns></returns>
|
||||
// 辅助函数:计算单个目标在某个技能下的价值
|
||||
private static double CalculateTargetValue(Character target, ISkill skill)
|
||||
{
|
||||
double value = Random.Shared.Next(1000);
|
||||
value += CalculateTargetValueEvent?.Invoke(target, skill) ?? 0;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,13 +306,25 @@ namespace Milimoe.FunGame.Core.Entity
|
||||
/// 在选取目标前向角色(玩家)发起询问
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <returns></returns>
|
||||
public virtual InquiryOptions? InquiryBeforeTargetSelection(Character character, Item item)
|
||||
public virtual InquiryOptions? InquiryBeforeTargetSelection(Character character, DecisionPoints dp)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理在选取目标前发起的询问的结果
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="response"></param>
|
||||
public virtual void ResolveInquiryBeforeTargetSelection(Character character, DecisionPoints dp, InquiryOptions options, InquiryResponse response)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 局内使用物品触发
|
||||
/// </summary>
|
||||
|
||||
@ -483,19 +483,43 @@ namespace Milimoe.FunGame.Core.Entity
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="normalAttack"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <returns></returns>
|
||||
public delegate InquiryOptions? NormalAttackInquiryOptionsDelegate(Character character, NormalAttack normalAttack);
|
||||
public delegate InquiryOptions? NormalAttackInquiryOptionsDelegate(Character character, NormalAttack normalAttack, DecisionPoints dp);
|
||||
public event NormalAttackInquiryOptionsDelegate? InquiryBeforeTargetSelectionEvent;
|
||||
/// <summary>
|
||||
/// 触发选择目标前的询问事件
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="normalAttack"></param>
|
||||
public InquiryOptions? OnInquiryBeforeTargetSelection(Character character, NormalAttack normalAttack)
|
||||
/// <param name="dp"></param>
|
||||
public InquiryOptions? OnInquiryBeforeTargetSelection(Character character, DecisionPoints dp)
|
||||
{
|
||||
return InquiryBeforeTargetSelectionEvent?.Invoke(character, normalAttack);
|
||||
return InquiryBeforeTargetSelectionEvent?.Invoke(character, this, dp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理在选取目标前发起询问的结果事件
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="normalAttack"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="response"></param>
|
||||
public delegate void ResolveInquiryBeforeTargetSelection(Character character, NormalAttack normalAttack, DecisionPoints dp, InquiryOptions options, InquiryResponse response);
|
||||
public event ResolveInquiryBeforeTargetSelection? ResolveInquiryBeforeTargetSelectionEvent;
|
||||
/// <summary>
|
||||
/// 触发处理选取目标前发起询问的结果事件
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="response"></param>
|
||||
public void OnResolveInquiryBeforeTargetSelection(Character character, DecisionPoints dp, InquiryOptions options, InquiryResponse response)
|
||||
{
|
||||
ResolveInquiryBeforeTargetSelectionEvent?.Invoke(character, this, dp, options, response);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
|
||||
@ -458,13 +458,25 @@ namespace Milimoe.FunGame.Core.Entity
|
||||
/// 在选取目标前向角色(玩家)发起询问
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="skill"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <returns></returns>
|
||||
public virtual InquiryOptions? InquiryBeforeTargetSelection(Character character, Skill skill)
|
||||
public virtual InquiryOptions? InquiryBeforeTargetSelection(Character character, DecisionPoints dp)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理在选取目标前发起的询问的结果
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="response"></param>
|
||||
public virtual void ResolveInquiryBeforeTargetSelection(Character character, DecisionPoints dp, InquiryOptions options, InquiryResponse response)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取可选择的目标列表
|
||||
/// </summary>
|
||||
|
||||
@ -1310,8 +1310,6 @@ namespace Milimoe.FunGame.Core.Model
|
||||
moved = CharacterMove(character, dp, aiDecision.TargetMoveGrid, startGrid);
|
||||
}
|
||||
|
||||
int costDP = dp.GetActionPointCost(type);
|
||||
|
||||
effects = [.. character.Effects.Where(e => e.IsInEffect).OrderByDescending(e => e.Priority)];
|
||||
foreach (Effect effect in effects)
|
||||
{
|
||||
@ -1346,6 +1344,16 @@ namespace Milimoe.FunGame.Core.Model
|
||||
}
|
||||
else if (type == CharacterActionType.NormalAttack)
|
||||
{
|
||||
// 使用普通攻击逻辑
|
||||
// 如果有询问,先进行询问
|
||||
character.NormalAttack.GamingQueue = this;
|
||||
if (character.NormalAttack.OnInquiryBeforeTargetSelection(character, dp) is InquiryOptions inquiry)
|
||||
{
|
||||
InquiryResponse response = Inquiry(character, inquiry);
|
||||
character.NormalAttack.OnResolveInquiryBeforeTargetSelection(character, dp, inquiry, response);
|
||||
}
|
||||
|
||||
int costDP = dp.GetActionPointCost(type);
|
||||
if (!forceAction && (character.CharacterState == CharacterState.NotActionable ||
|
||||
character.CharacterState == CharacterState.ActionRestricted ||
|
||||
character.CharacterState == CharacterState.BattleRestricted ||
|
||||
@ -1363,13 +1371,6 @@ namespace Milimoe.FunGame.Core.Model
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用普通攻击逻辑
|
||||
// 如果有询问,先进行询问
|
||||
character.NormalAttack.GamingQueue = this;
|
||||
if (character.NormalAttack.OnInquiryBeforeTargetSelection(character, character.NormalAttack) is InquiryOptions inquiry)
|
||||
{
|
||||
Inquiry(character, inquiry);
|
||||
}
|
||||
// 选择目标
|
||||
List<Character> targets;
|
||||
if (aiDecision != null)
|
||||
@ -1446,21 +1447,22 @@ namespace Milimoe.FunGame.Core.Model
|
||||
skill.GamingQueue = this;
|
||||
List<Character> targets = [];
|
||||
List<Grid> grids = [];
|
||||
costDP = dp.GetActionPointCost(type, skill);
|
||||
if (skill.SkillType == SkillType.Magic)
|
||||
{
|
||||
// 如果有询问,先进行询问
|
||||
if (skill.InquiryBeforeTargetSelection(character, dp) is InquiryOptions inquiry)
|
||||
{
|
||||
InquiryResponse response = Inquiry(character, inquiry);
|
||||
skill.ResolveInquiryBeforeTargetSelection(character, dp, inquiry, response);
|
||||
}
|
||||
|
||||
int costDP = dp.GetActionPointCost(type, skill);
|
||||
if (dp.CurrentDecisionPoints < costDP)
|
||||
{
|
||||
if (IsDebug) WriteLine($"[ {character} ] 想要释放 [ {skill.Name} ],但决策点不足,无法释放技能!");
|
||||
}
|
||||
else if (skill.SkillType == SkillType.Magic)
|
||||
else if (CheckCanCast(character, skill, out double cost))
|
||||
{
|
||||
if (CheckCanCast(character, skill, out double cost))
|
||||
{
|
||||
// 如果有询问,先进行询问
|
||||
if (skill.InquiryBeforeTargetSelection(character, skill) is InquiryOptions inquiry)
|
||||
{
|
||||
Inquiry(character, inquiry);
|
||||
}
|
||||
|
||||
// 吟唱前需要先选取目标
|
||||
List<Grid> castRange = [];
|
||||
if (_map != null && realGrid != null)
|
||||
@ -1510,7 +1512,17 @@ namespace Milimoe.FunGame.Core.Model
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (skill is CourageCommandSkill && dp.CourageCommandSkill)
|
||||
else
|
||||
{
|
||||
// 如果有询问,先进行询问
|
||||
if (skill.InquiryBeforeTargetSelection(character, dp) is InquiryOptions inquiry)
|
||||
{
|
||||
InquiryResponse response = Inquiry(character, inquiry);
|
||||
skill.ResolveInquiryBeforeTargetSelection(character, dp, inquiry, response);
|
||||
}
|
||||
|
||||
int costDP = dp.GetActionPointCost(type, skill);
|
||||
if (skill is CourageCommandSkill && dp.CourageCommandSkill)
|
||||
{
|
||||
if (IsDebug) WriteLine($"[ {character} ] 想要释放 [ {skill.Name} ],但该回合已经使用过勇气指令,无法再次使用勇气指令!");
|
||||
}
|
||||
@ -1522,17 +1534,9 @@ namespace Milimoe.FunGame.Core.Model
|
||||
{
|
||||
if (IsDebug) WriteLine($"[ {character} ] 想要释放 [ {skill.Name} ],但该回合使用爆发技的次数已超过决策点配额,无法再次使用爆发技!");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 只有魔法需要吟唱,战技和爆发技直接释放
|
||||
if (CheckCanCast(character, skill, out double cost))
|
||||
else if (CheckCanCast(character, skill, out double cost))
|
||||
{
|
||||
// 如果有询问,先进行询问
|
||||
if (skill.InquiryBeforeTargetSelection(character, skill) is InquiryOptions inquiry)
|
||||
{
|
||||
Inquiry(character, inquiry);
|
||||
}
|
||||
|
||||
List<Grid> castRange = [];
|
||||
if (_map != null && realGrid != null)
|
||||
{
|
||||
@ -1686,7 +1690,6 @@ namespace Milimoe.FunGame.Core.Model
|
||||
{
|
||||
WriteLine($"[ {character} ] 想要释放 [ {skill.Name} ],但是没有目标!");
|
||||
}
|
||||
_castingSkills.Remove(character);
|
||||
WriteLine($"[ {character} ] 放弃释放技能!");
|
||||
character.CharacterState = CharacterState.Actionable;
|
||||
character.UpdateCharacterState();
|
||||
@ -1755,7 +1758,6 @@ namespace Milimoe.FunGame.Core.Model
|
||||
}
|
||||
else
|
||||
{
|
||||
_castingSuperSkills.Remove(character);
|
||||
WriteLine($"[ {character} ] 因能量不足放弃释放爆发技!");
|
||||
character.CharacterState = CharacterState.Actionable;
|
||||
character.UpdateCharacterState();
|
||||
@ -1787,14 +1789,17 @@ namespace Milimoe.FunGame.Core.Model
|
||||
Skill skill = item.Skills.Active;
|
||||
|
||||
// 如果有询问,先进行询问
|
||||
if (item.InquiryBeforeTargetSelection(character, item) is InquiryOptions inquiry)
|
||||
if (item.InquiryBeforeTargetSelection(character, dp) is InquiryOptions inquiry)
|
||||
{
|
||||
Inquiry(character, inquiry);
|
||||
InquiryResponse response = Inquiry(character, inquiry);
|
||||
item.ResolveInquiryBeforeTargetSelection(character, dp, inquiry, response);
|
||||
}
|
||||
|
||||
if (skill.InquiryBeforeTargetSelection(character, skill) is InquiryOptions inquiry2)
|
||||
// 如果有询问,先进行询问
|
||||
if (skill.InquiryBeforeTargetSelection(character, dp) is InquiryOptions inquiry2)
|
||||
{
|
||||
Inquiry(character, inquiry2);
|
||||
InquiryResponse response = Inquiry(character, inquiry2);
|
||||
skill.ResolveInquiryBeforeTargetSelection(character, dp, inquiry2, response);
|
||||
}
|
||||
|
||||
List<Grid> castRange = [];
|
||||
@ -1804,6 +1809,8 @@ namespace Milimoe.FunGame.Core.Model
|
||||
enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)];
|
||||
teammates = [.. teammates.Where(castRange.SelectMany(g => g.Characters).Contains)];
|
||||
}
|
||||
|
||||
int costDP = dp.GetActionPointCost(type);
|
||||
if (dp.CurrentDecisionPoints < costDP)
|
||||
{
|
||||
if (IsDebug) WriteLine($"[ {character} ] 想要使用物品 [ {item.Name} ],但决策点不足,无法使用物品!");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user