Initial import of BardFrontline

This commit is contained in:
2026-10-13 22:55:05 +08:00
commit f3ab7c58c3
24 changed files with 2917 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
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; }
}