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.Interface.Entity;
|
||||||
using Milimoe.FunGame.Core.Library.Common.Addon;
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
using Milimoe.FunGame.Core.Library.Constant;
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
@ -11,8 +12,19 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
private readonly GamingQueue _queue = queue;
|
private readonly GamingQueue _queue = queue;
|
||||||
private readonly GameMap _map = map;
|
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>
|
/// <summary>
|
||||||
/// AI的核心决策方法,根据当前游戏状态为角色选择最佳行动
|
/// 核心决策方法:外部同步,内部异步并行计算所有独立决策的分数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="character">当前行动的AI角色</param>
|
/// <param name="character">当前行动的AI角色</param>
|
||||||
/// <param name="dp">角色的决策点</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<Skill> availableSkills, List<Item> availableItems, List<Character> allEnemysInGame, List<Character> allTeammatesInGame,
|
||||||
List<Character> selectableEnemys, List<Character> selectableTeammates, double pUseItem, double pCastSkill, double pNormalAttack)
|
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 dynamicPUseItem = pUseItem;
|
||||||
double dynamicPCastSkill = pCastSkill;
|
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)
|
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);
|
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<Grid> normalAttackReachableGrids = _map.GetGridsByRange(potentialMoveGrid, character.ATR, true);
|
||||||
|
List<Character> normalAttackReachableEnemys = [.. allEnemysInGame.Where(c =>
|
||||||
List<Character> normalAttackReachableEnemys = [.. allEnemysInGame.Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)).Distinct()];
|
normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c)
|
||||||
List<Character> normalAttackReachableTeammates = [.. allTeammatesInGame.Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c)).Distinct()];
|
&& !c.IsUnselectable
|
||||||
|
&& selectableEnemys.Contains(c)).Distinct()];
|
||||||
|
|
||||||
if (normalAttackReachableEnemys.Count > 0)
|
if (normalAttackReachableEnemys.Count > 0)
|
||||||
{
|
{
|
||||||
// 将筛选后的目标列表传递给 SelectTargets
|
List<Character> targets = SelectTargets(character, character.NormalAttack, normalAttackReachableEnemys, []);
|
||||||
List<Character> targets = SelectTargets(character, character.NormalAttack, normalAttackReachableEnemys, normalAttackReachableTeammates);
|
|
||||||
if (targets.Count > 0)
|
if (targets.Count > 0)
|
||||||
{
|
{
|
||||||
double currentScore = EvaluateNormalAttack(character, targets) - movePenalty;
|
double currentScore = EvaluateNormalAttack(character, targets) - movePenalty;
|
||||||
double probabilityWeight = 1.0 + (normalizedPNormalAttack * 0.3);
|
double probabilityWeight = 1.0 + (normalizedPNormalAttack * 0.3);
|
||||||
candidateDecisions.Add(new AIDecision
|
AIDecision attackDecision = new()
|
||||||
{
|
{
|
||||||
ActionType = CharacterActionType.NormalAttack,
|
ActionType = CharacterActionType.NormalAttack,
|
||||||
TargetMoveGrid = potentialMoveGrid,
|
TargetMoveGrid = potentialMoveGrid,
|
||||||
@ -92,45 +175,60 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
Targets = targets,
|
Targets = targets,
|
||||||
Score = currentScore,
|
Score = currentScore,
|
||||||
ProbabilityWeight = probabilityWeight
|
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)
|
foreach (Skill skill in availableSkills)
|
||||||
{
|
{
|
||||||
if (CanCharacterUseSkill(character, skill, dp) && _queue.CheckCanCast(character, skill, out double cost))
|
if (CanCharacterUseSkill(character, skill, dp) && _queue.CheckCanCast(character, skill, out double cost))
|
||||||
{
|
{
|
||||||
// 计算当前技能的可达格子
|
|
||||||
List<Grid> skillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true);
|
List<Grid> skillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true);
|
||||||
|
|
||||||
if (skill.IsNonDirectional)
|
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
|
else
|
||||||
{
|
{
|
||||||
List<Character> skillReachableEnemys = [.. allEnemysInGame.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)).Distinct()];
|
List<Character> skillReachableEnemys = [.. allEnemysInGame.Where(c =>
|
||||||
List<Character> skillReachableTeammates = [.. allTeammatesInGame.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c)).Distinct()];
|
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)
|
if (skillReachableEnemys.Count > 0 || skillReachableTeammates.Count > 0)
|
||||||
{
|
{
|
||||||
// 将筛选后的目标列表传递给 SelectTargets
|
|
||||||
List<Character> targets = SelectTargets(character, skill, skillReachableEnemys, skillReachableTeammates);
|
List<Character> targets = SelectTargets(character, skill, skillReachableEnemys, skillReachableTeammates);
|
||||||
if (targets.Count > 0)
|
if (targets.Count > 0)
|
||||||
{
|
{
|
||||||
double currentScore = EvaluateSkill(character, skill, targets, cost) - movePenalty;
|
double currentScore = EvaluateSkill(character, skill, targets, cost) - movePenalty;
|
||||||
double probabilityWeight = 1.0 + (normalizedPCastSkill * 0.3);
|
double probabilityWeight = 1.0 + (normalizedPCastSkill * 0.3);
|
||||||
candidateDecisions.Add(new AIDecision
|
AIDecision skillDecision = new()
|
||||||
{
|
{
|
||||||
ActionType = CharacterActionType.PreCastSkill,
|
ActionType = CharacterActionType.PreCastSkill,
|
||||||
TargetMoveGrid = potentialMoveGrid,
|
TargetMoveGrid = potentialMoveGrid,
|
||||||
@ -138,7 +236,13 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
Targets = targets,
|
Targets = targets,
|
||||||
Score = currentScore,
|
Score = currentScore,
|
||||||
ProbabilityWeight = probabilityWeight
|
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)
|
foreach (Item item in availableItems)
|
||||||
{
|
{
|
||||||
if (item.Skills.Active != null && CanCharacterUseItem(character, item, dp) && _queue.CheckCanCast(character, item.Skills.Active, out double cost))
|
if (item.Skills.Active != null && CanCharacterUseItem(character, item, dp) && _queue.CheckCanCast(character, item.Skills.Active, out double cost))
|
||||||
{
|
{
|
||||||
Skill itemSkill = item.Skills.Active;
|
Skill itemSkill = item.Skills.Active;
|
||||||
|
|
||||||
// 计算当前物品技能的可达格子
|
|
||||||
List<Grid> itemSkillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, itemSkill.CastRange, true);
|
List<Grid> itemSkillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, itemSkill.CastRange, true);
|
||||||
|
|
||||||
if (itemSkill.IsNonDirectional)
|
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
|
else
|
||||||
{
|
{
|
||||||
List<Character> itemSkillReachableEnemys = [.. allEnemysInGame.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)).Distinct()];
|
List<Character> itemSkillReachableEnemys = [.. allEnemysInGame.Where(c =>
|
||||||
List<Character> itemSkillReachableTeammates = [.. allTeammatesInGame.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c)).Distinct()];
|
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)
|
if (itemSkillReachableEnemys.Count > 0 || itemSkillReachableTeammates.Count > 0)
|
||||||
{
|
{
|
||||||
// 将筛选后的目标列表传递给 SelectTargets
|
|
||||||
List<Character> targetsForItem = SelectTargets(character, itemSkill, itemSkillReachableEnemys, itemSkillReachableTeammates);
|
List<Character> targetsForItem = SelectTargets(character, itemSkill, itemSkillReachableEnemys, itemSkillReachableTeammates);
|
||||||
if (targetsForItem.Count > 0)
|
if (targetsForItem.Count > 0)
|
||||||
{
|
{
|
||||||
double currentScore = EvaluateItem(character, item, targetsForItem, cost) - movePenalty;
|
double currentScore = EvaluateItem(character, item, targetsForItem, cost) - movePenalty;
|
||||||
double probabilityWeight = 1.0 + (normalizedPUseItem * 0.3);
|
double probabilityWeight = 1.0 + (normalizedPUseItem * 0.3);
|
||||||
candidateDecisions.Add(new AIDecision
|
AIDecision itemDecision = new()
|
||||||
{
|
{
|
||||||
ActionType = CharacterActionType.UseItem,
|
ActionType = CharacterActionType.UseItem,
|
||||||
TargetMoveGrid = potentialMoveGrid,
|
TargetMoveGrid = potentialMoveGrid,
|
||||||
@ -189,21 +302,25 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
Targets = targetsForItem,
|
Targets = targetsForItem,
|
||||||
Score = currentScore,
|
Score = currentScore,
|
||||||
ProbabilityWeight = probabilityWeight
|
ProbabilityWeight = probabilityWeight
|
||||||
});
|
};
|
||||||
}
|
// 偏好类型加分
|
||||||
}
|
if (preferredAction.HasValue && itemDecision.ActionType == preferredAction.Value)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果从该格子没有更好的行动,但移动本身有价值
|
|
||||||
// 只有当当前最佳决策是“结束回合”或分数很低时,才考虑纯粹的移动
|
|
||||||
if (potentialMoveGrid != startGrid && bestDecision.Score < 0) // 如果当前最佳决策是负分(即什么都不做)
|
|
||||||
{
|
{
|
||||||
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> tempAttackGridsForPureMove = _map.GetGridsByRange(potentialMoveGrid, character.ATR, true);
|
||||||
List<Grid> tempCastGridsForPureMove = [];
|
List<Grid> tempCastGridsForPureMove = [];
|
||||||
foreach (Skill skill in availableSkills)
|
foreach (Skill skill in availableSkills)
|
||||||
@ -218,53 +335,44 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<Grid> tempAllReachableGridsForPureMove = [.. tempAttackGridsForPureMove.Union(tempCastGridsForPureMove).Distinct()];
|
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)
|
if (target != null)
|
||||||
{
|
{
|
||||||
Grid? nearestEnemyGrid = _map.GetCharacterCurrentGrid(target);
|
Grid? nearestEnemyGrid = _map.GetCharacterCurrentGrid(target);
|
||||||
if (nearestEnemyGrid != null)
|
if (nearestEnemyGrid != null)
|
||||||
{
|
{
|
||||||
// 奖励靠近敌人
|
|
||||||
pureMoveScore += (10 - GameMap.CalculateManhattanDistance(potentialMoveGrid, nearestEnemyGrid)) * 0.1;
|
pureMoveScore += (10 - GameMap.CalculateManhattanDistance(potentialMoveGrid, nearestEnemyGrid)) * 0.1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
candidateDecisions.Add(new AIDecision
|
AIDecision moveDecision = new()
|
||||||
{
|
{
|
||||||
ActionType = CharacterActionType.Move,
|
ActionType = CharacterActionType.Move,
|
||||||
TargetMoveGrid = potentialMoveGrid,
|
TargetMoveGrid = potentialMoveGrid,
|
||||||
Targets = [],
|
Targets = [],
|
||||||
Score = pureMoveScore,
|
Score = pureMoveScore,
|
||||||
IsPureMove = true
|
IsPureMove = true,
|
||||||
});
|
ProbabilityWeight = 1.0 // 纯移动无概率权重
|
||||||
}
|
};
|
||||||
|
// 偏好类型加分(如果移动是偏好类型)
|
||||||
// 偏好类型额外加分
|
if (preferredAction.HasValue && moveDecision.ActionType == preferredAction.Value)
|
||||||
if (preferredAction.HasValue)
|
|
||||||
{
|
{
|
||||||
foreach (var decision in candidateDecisions)
|
moveDecision.Score *= 1.2;
|
||||||
{
|
|
||||||
if (decision.ActionType == preferredAction.Value)
|
|
||||||
{
|
|
||||||
decision.Score *= 1.2;
|
|
||||||
}
|
}
|
||||||
|
candidateDecisions.Add(moveDecision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最终决策
|
|
||||||
bestDecision = candidateDecisions.OrderByDescending(d => d.Score * d.ProbabilityWeight).FirstOrDefault() ?? bestDecision;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bestDecision;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- AI 决策辅助方法 ---
|
// --- AI 决策辅助方法 ---
|
||||||
|
|
||||||
// 获取偏好行动类型
|
// 获取偏好行动类型
|
||||||
@ -343,42 +451,39 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
// 检查角色是否能进行普通攻击(基于状态)
|
// 检查角色是否能进行普通攻击(基于状态)
|
||||||
private static bool CanCharacterNormalAttack(Character character, DecisionPoints dp)
|
private static bool CanCharacterNormalAttack(Character character, DecisionPoints dp)
|
||||||
{
|
{
|
||||||
return dp.CheckActionTypeQuota(CharacterActionType.NormalAttack) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostNormalAttack &&
|
return dp.CheckActionTypeQuota(CharacterActionType.NormalAttack)
|
||||||
character.CharacterState != CharacterState.NotActionable &&
|
&& dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostNormalAttack
|
||||||
character.CharacterState != CharacterState.ActionRestricted &&
|
&& character.CharacterState != CharacterState.NotActionable
|
||||||
character.CharacterState != CharacterState.BattleRestricted &&
|
&& character.CharacterState != CharacterState.ActionRestricted
|
||||||
character.CharacterState != CharacterState.AttackRestricted;
|
&& character.CharacterState != CharacterState.BattleRestricted
|
||||||
|
&& character.CharacterState != CharacterState.AttackRestricted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查角色是否能使用某个技能(基于状态)
|
// 检查角色是否能使用某个技能(基于状态)
|
||||||
private static bool CanCharacterUseSkill(Character character, Skill skill, DecisionPoints dp)
|
private static bool CanCharacterUseSkill(Character character, Skill skill, DecisionPoints dp)
|
||||||
{
|
{
|
||||||
return ((skill.SkillType == SkillType.Magic && dp.CheckActionTypeQuota(CharacterActionType.PreCastSkill) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostMagic) ||
|
return (
|
||||||
(skill.SkillType == SkillType.Skill && dp.CheckActionTypeQuota(CharacterActionType.CastSkill) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostSkill) ||
|
(skill.SkillType == SkillType.Magic && dp.CheckActionTypeQuota(CharacterActionType.PreCastSkill) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostMagic)
|
||||||
(skill.SkillType == SkillType.SuperSkill && dp.CheckActionTypeQuota(CharacterActionType.CastSuperSkill)) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostSuperSkill) &&
|
|| (skill.SkillType == SkillType.Skill && dp.CheckActionTypeQuota(CharacterActionType.CastSkill) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostSkill)
|
||||||
character.CharacterState != CharacterState.NotActionable &&
|
|| (skill.SkillType == SkillType.SuperSkill && dp.CheckActionTypeQuota(CharacterActionType.CastSuperSkill) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostSuperSkill)
|
||||||
character.CharacterState != CharacterState.ActionRestricted &&
|
)
|
||||||
character.CharacterState != CharacterState.BattleRestricted &&
|
&& character.CharacterState != CharacterState.NotActionable
|
||||||
character.CharacterState != CharacterState.SkillRestricted;
|
&& character.CharacterState != CharacterState.ActionRestricted
|
||||||
|
&& character.CharacterState != CharacterState.BattleRestricted
|
||||||
|
&& character.CharacterState != CharacterState.SkillRestricted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查角色是否能使用某个物品(基于状态)
|
// 检查角色是否能使用某个物品(基于状态)
|
||||||
private static bool CanCharacterUseItem(Character character, Item item, DecisionPoints dp)
|
private static bool CanCharacterUseItem(Character character, Item item, DecisionPoints dp)
|
||||||
{
|
{
|
||||||
return dp.CheckActionTypeQuota(CharacterActionType.UseItem) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostItem &&
|
return dp.CheckActionTypeQuota(CharacterActionType.UseItem)
|
||||||
character.CharacterState != CharacterState.NotActionable &&
|
&& dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostItem
|
||||||
(character.CharacterState != CharacterState.ActionRestricted || item.ItemType == ItemType.Consumable) && // 行动受限只能用消耗品
|
&& character.CharacterState != CharacterState.NotActionable
|
||||||
character.CharacterState != CharacterState.BattleRestricted;
|
&& (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)
|
private static List<Character> SelectTargets(Character character, ISkill skill, List<Character> enemys, List<Character> teammates)
|
||||||
{
|
{
|
||||||
List<Character> targets = skill.GetSelectableTargets(character, enemys, 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)];
|
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)
|
private static double EvaluateNormalAttack(Character character, List<Character> targets)
|
||||||
{
|
{
|
||||||
double score = 0;
|
double score = 0;
|
||||||
@ -401,24 +501,16 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
score += damage;
|
score += damage;
|
||||||
if (target.HP <= damage) score += 100;
|
if (target.HP <= damage) score += 100;
|
||||||
}
|
}
|
||||||
|
score += EvaluateNormalAttackEvent?.Invoke(character, character.NormalAttack, targets) ?? 0;
|
||||||
return score;
|
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)
|
private static double EvaluateSkill(Character character, Skill skill, List<Character> targets, double cost)
|
||||||
{
|
{
|
||||||
double score = 0;
|
double score = 0;
|
||||||
score += targets.Sum(t => CalculateTargetValue(t, skill));
|
score += targets.Sum(t => CalculateTargetValue(t, skill));
|
||||||
//score -= cost * 5;
|
score += EvaluateSkillEvent?.Invoke(character, skill, targets, cost) ?? 0;
|
||||||
//score -= skill.RealCD * 2;
|
|
||||||
//score -= skill.HardnessTime * 2;
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,6 +547,7 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
|
|
||||||
double movePenalty = GameMap.CalculateManhattanDistance(_map.GetCharacterCurrentGrid(character)!, moveGrid) * 0.5;
|
double movePenalty = GameMap.CalculateManhattanDistance(_map.GetCharacterCurrentGrid(character)!, moveGrid) * 0.5;
|
||||||
double finalScore = bestSkillScore - movePenalty;
|
double finalScore = bestSkillScore - movePenalty;
|
||||||
|
finalScore += EvaluateNonDirectionalSkillEvent?.Invoke(character, skill, moveGrid, castableGrids, allEnemys, allTeammates, cost) ?? 0;
|
||||||
|
|
||||||
return new AIDecision
|
return new AIDecision
|
||||||
{
|
{
|
||||||
@ -463,33 +556,24 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
SkillToUse = skill,
|
SkillToUse = skill,
|
||||||
Targets = [],
|
Targets = [],
|
||||||
TargetGrids = bestTargetGrids,
|
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)
|
private static double EvaluateItem(Character character, Item item, List<Character> targets, double cost)
|
||||||
{
|
{
|
||||||
double score = Random.Shared.Next(1000);
|
double score = Random.Shared.Next(1000);
|
||||||
|
score += EvaluateItemEvent?.Invoke(character, item, targets, cost) ?? 0;
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// 辅助函数:计算单个目标在某个技能下的价值
|
||||||
/// 辅助函数:计算单个目标在某个技能下的价值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="target"></param>
|
|
||||||
/// <param name="skill"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static double CalculateTargetValue(Character target, ISkill skill)
|
private static double CalculateTargetValue(Character target, ISkill skill)
|
||||||
{
|
{
|
||||||
double value = Random.Shared.Next(1000);
|
double value = Random.Shared.Next(1000);
|
||||||
|
value += CalculateTargetValueEvent?.Invoke(target, skill) ?? 0;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -306,13 +306,25 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// 在选取目标前向角色(玩家)发起询问
|
/// 在选取目标前向角色(玩家)发起询问
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="character"></param>
|
/// <param name="character"></param>
|
||||||
/// <param name="item"></param>
|
/// <param name="dp"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public virtual InquiryOptions? InquiryBeforeTargetSelection(Character character, Item item)
|
public virtual InquiryOptions? InquiryBeforeTargetSelection(Character character, DecisionPoints dp)
|
||||||
{
|
{
|
||||||
return null;
|
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>
|
||||||
/// 局内使用物品触发
|
/// 局内使用物品触发
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -483,19 +483,43 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="character"></param>
|
/// <param name="character"></param>
|
||||||
/// <param name="normalAttack"></param>
|
/// <param name="normalAttack"></param>
|
||||||
|
/// <param name="dp"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public delegate InquiryOptions? NormalAttackInquiryOptionsDelegate(Character character, NormalAttack normalAttack);
|
public delegate InquiryOptions? NormalAttackInquiryOptionsDelegate(Character character, NormalAttack normalAttack, DecisionPoints dp);
|
||||||
public event NormalAttackInquiryOptionsDelegate? InquiryBeforeTargetSelectionEvent;
|
public event NormalAttackInquiryOptionsDelegate? InquiryBeforeTargetSelectionEvent;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 触发选择目标前的询问事件
|
/// 触发选择目标前的询问事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="character"></param>
|
/// <param name="character"></param>
|
||||||
/// <param name="normalAttack"></param>
|
/// <param name="dp"></param>
|
||||||
public InquiryOptions? OnInquiryBeforeTargetSelection(Character character, NormalAttack normalAttack)
|
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>
|
||||||
/// 等级
|
/// 等级
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -458,13 +458,25 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// 在选取目标前向角色(玩家)发起询问
|
/// 在选取目标前向角色(玩家)发起询问
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="character"></param>
|
/// <param name="character"></param>
|
||||||
/// <param name="skill"></param>
|
/// <param name="dp"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public virtual InquiryOptions? InquiryBeforeTargetSelection(Character character, Skill skill)
|
public virtual InquiryOptions? InquiryBeforeTargetSelection(Character character, DecisionPoints dp)
|
||||||
{
|
{
|
||||||
return null;
|
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>
|
||||||
/// 获取可选择的目标列表
|
/// 获取可选择的目标列表
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1310,8 +1310,6 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
moved = CharacterMove(character, dp, aiDecision.TargetMoveGrid, startGrid);
|
moved = CharacterMove(character, dp, aiDecision.TargetMoveGrid, startGrid);
|
||||||
}
|
}
|
||||||
|
|
||||||
int costDP = dp.GetActionPointCost(type);
|
|
||||||
|
|
||||||
effects = [.. character.Effects.Where(e => e.IsInEffect).OrderByDescending(e => e.Priority)];
|
effects = [.. character.Effects.Where(e => e.IsInEffect).OrderByDescending(e => e.Priority)];
|
||||||
foreach (Effect effect in effects)
|
foreach (Effect effect in effects)
|
||||||
{
|
{
|
||||||
@ -1346,6 +1344,16 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
}
|
}
|
||||||
else if (type == CharacterActionType.NormalAttack)
|
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 ||
|
if (!forceAction && (character.CharacterState == CharacterState.NotActionable ||
|
||||||
character.CharacterState == CharacterState.ActionRestricted ||
|
character.CharacterState == CharacterState.ActionRestricted ||
|
||||||
character.CharacterState == CharacterState.BattleRestricted ||
|
character.CharacterState == CharacterState.BattleRestricted ||
|
||||||
@ -1363,13 +1371,6 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 使用普通攻击逻辑
|
|
||||||
// 如果有询问,先进行询问
|
|
||||||
character.NormalAttack.GamingQueue = this;
|
|
||||||
if (character.NormalAttack.OnInquiryBeforeTargetSelection(character, character.NormalAttack) is InquiryOptions inquiry)
|
|
||||||
{
|
|
||||||
Inquiry(character, inquiry);
|
|
||||||
}
|
|
||||||
// 选择目标
|
// 选择目标
|
||||||
List<Character> targets;
|
List<Character> targets;
|
||||||
if (aiDecision != null)
|
if (aiDecision != null)
|
||||||
@ -1446,21 +1447,22 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
skill.GamingQueue = this;
|
skill.GamingQueue = this;
|
||||||
List<Character> targets = [];
|
List<Character> targets = [];
|
||||||
List<Grid> grids = [];
|
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 (dp.CurrentDecisionPoints < costDP)
|
||||||
{
|
{
|
||||||
if (IsDebug) WriteLine($"[ {character} ] 想要释放 [ {skill.Name} ],但决策点不足,无法释放技能!");
|
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 = [];
|
List<Grid> castRange = [];
|
||||||
if (_map != null && realGrid != null)
|
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} ],但该回合已经使用过勇气指令,无法再次使用勇气指令!");
|
if (IsDebug) WriteLine($"[ {character} ] 想要释放 [ {skill.Name} ],但该回合已经使用过勇气指令,无法再次使用勇气指令!");
|
||||||
}
|
}
|
||||||
@ -1522,17 +1534,9 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
{
|
{
|
||||||
if (IsDebug) WriteLine($"[ {character} ] 想要释放 [ {skill.Name} ],但该回合使用爆发技的次数已超过决策点配额,无法再次使用爆发技!");
|
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 = [];
|
List<Grid> castRange = [];
|
||||||
if (_map != null && realGrid != null)
|
if (_map != null && realGrid != null)
|
||||||
{
|
{
|
||||||
@ -1686,7 +1690,6 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
{
|
{
|
||||||
WriteLine($"[ {character} ] 想要释放 [ {skill.Name} ],但是没有目标!");
|
WriteLine($"[ {character} ] 想要释放 [ {skill.Name} ],但是没有目标!");
|
||||||
}
|
}
|
||||||
_castingSkills.Remove(character);
|
|
||||||
WriteLine($"[ {character} ] 放弃释放技能!");
|
WriteLine($"[ {character} ] 放弃释放技能!");
|
||||||
character.CharacterState = CharacterState.Actionable;
|
character.CharacterState = CharacterState.Actionable;
|
||||||
character.UpdateCharacterState();
|
character.UpdateCharacterState();
|
||||||
@ -1755,7 +1758,6 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_castingSuperSkills.Remove(character);
|
|
||||||
WriteLine($"[ {character} ] 因能量不足放弃释放爆发技!");
|
WriteLine($"[ {character} ] 因能量不足放弃释放爆发技!");
|
||||||
character.CharacterState = CharacterState.Actionable;
|
character.CharacterState = CharacterState.Actionable;
|
||||||
character.UpdateCharacterState();
|
character.UpdateCharacterState();
|
||||||
@ -1787,14 +1789,17 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
Skill skill = item.Skills.Active;
|
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 = [];
|
List<Grid> castRange = [];
|
||||||
@ -1804,6 +1809,8 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)];
|
enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)];
|
||||||
teammates = [.. teammates.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 (dp.CurrentDecisionPoints < costDP)
|
||||||
{
|
{
|
||||||
if (IsDebug) WriteLine($"[ {character} ] 想要使用物品 [ {item.Name} ],但决策点不足,无法使用物品!");
|
if (IsDebug) WriteLine($"[ {character} ] 想要使用物品 [ {item.Name} ],但决策点不足,无法使用物品!");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user