Files
BardFrontline/Core/DecisionAudit.cs
T

56 lines
3.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace BardFrontline.Core;
public sealed record SkillAudit(Skill Skill, ulong TargetId, string Result);
public sealed record DecisionFrame(long Number, double Time, string Phase, ActionDecision? Decision, IReadOnlyList<SkillAudit> Skills);
public static class DecisionAudit
{
public static IReadOnlyList<SkillAudit> Evaluate(CombatSnapshot? s, Settings c, ActionDecision? selected,
string phase, Func<Skill, Actor, string?> nativeCheck, Func<Skill, Actor, bool>? targetProbe = null)
{
var result = new List<SkillAudit>();
var cache = new Dictionary<(Skill, ulong), string?>();
string? Check(Skill skill, Actor target)
{
if (!cache.TryGetValue((skill, target.Id), out var value)) cache[(skill, target.Id)] = value = nativeCheck(skill, target);
return value;
}
var hasEnemy = s != null && s.Enemies.Any(a => Actions.All.Any(skill => TargetSelector.Relevant(skill, s, c)
&& TargetSelector.Allowed(a, c, skill) && (targetProbe?.Invoke(skill, a) ?? Check(skill, a) == null)));
var pool = s == null ? [] : TargetSelector.CombatCandidates(s, c, new Availability(Check));
foreach (var skill in Actions.All)
{
if (selected?.Skill == skill)
{ result.Add(new(skill, selected.TargetId, $"本轮选中 → {selected.TargetId:X}:{selected.Reason}")); continue; }
var reason = s == null ? phase : Strategy.PolicyReason(skill, s, c);
if (reason == null && skill is Skill.Bravery or Skill.Dervish or Skill.FinalFantasia && !hasEnemy) reason = "没有可攻击的增益目标";
if (reason == null && skill == Skill.Sprint && hasEnemy) reason = "存在有效攻击目标,不自动冲刺";
if (reason != null) { result.Add(new(skill, 0, reason)); continue; }
var state = s!;
IEnumerable<Actor> candidates = Actions.IsHostile(skill) ? pool
: skill == Skill.WardensPaean ? new[] { state.Player }.Concat(c.HelpParty ? state.Party : [])
: [state.Player];
var failures = new HashSet<string>();
Actor? usable = null;
foreach (var target in TargetSelector.Ordered(candidates, c, state.CurrentTarget).OrderBy(a => a.IsObjective))
{
if (Actions.IsHostile(skill) && !TargetSelector.Allowed(target, c, skill))
{ failures.Add("目标被死亡 / 防御 / 免疫 / 分类设置排除"); continue; }
if (skill == Skill.EagleEyeShot && c.EagleStrategy == EagleStrategy.FinisherOnly && target.EffectiveHp > (ulong)c.EagleFinisherHp)
{ failures.Add("严格收割:有效 HP 高于阈值"); continue; }
if (skill == Skill.WardensPaean && (!target.Alive || !target.Targetable || !Statuses.Controls.Any(target.Has))) continue;
var failure = Check(skill, target);
if (failure != null) { failures.Add(failure); continue; }
usable = target; break;
}
result.Add(usable == null
? new(skill, 0, failures.Count == 0 ? "没有符合策略的目标" : string.Join(";", failures.Take(4)))
: new(skill, usable.Id, selected != null ? $"可用 → {usable.Name};本轮优先选择 {Actions.Name(selected.Skill)}"
: $"就绪候选 → {usable.Name};当前执行阶段:{phase}"));
}
return result;
}
private sealed class Availability(Func<Skill, Actor, string?> check) : IActionAvailability
{ public bool CanUse(Skill skill, Actor target) => check(skill, target) == null; }
}