using System.Numerics; namespace BardFrontline.Core; public sealed record AoeShape(bool Line, float Radius, float HalfWidth = 2); public static class Strategy { public static int FocusCount(CombatSnapshot s) => s.Enemies.Count(a => a.Enemy && a.Alive && a.Distance <= 30 && (!a.IsObjective || a.ThreateningObjective) && (a.TargetId == s.Player.Id || a.CastTargetId == s.Player.Id)); public static bool FocusDanger(CombatSnapshot s, Settings c) => c.FocusDefense && s.InCombat && (FocusCount(s) >= c.FocusAttackers || s.RecentHpLossRatio >= .25); public static bool GuardNeeded(CombatSnapshot s, Settings c) => s.Player.HpRatio * 100 <= c.GuardHpPercent || FocusDanger(s, c) && s.Player.HpRatio * 100 <= c.FocusGuardPercent; public static int HealDeficit(CombatSnapshot s, Settings c) => FocusDanger(s, c) ? Math.Min(c.RecuperateMissingHp, 6000) : c.RecuperateMissingHp; public static string? HoldReason(Skill skill, CombatSnapshot s, Settings c) { if (skill == Skill.RepellingShot && c.ManualRepelling) return "后跳由玩家手动释放"; var burstSkill = skill is Skill.HarmonicArrow or Skill.Bravery or Skill.Dervish or Skill.FinalFantasia; if (!burstSkill || c.Burst is BurstStrategy.Normal or BurstStrategy.Pressure || s.BurstWindow) return null; if (c.Burst == BurstStrategy.ManualWindow) return "保留爆发:等待手动开启爆发窗口"; if (skill != Skill.HarmonicArrow || s.HarmonicCharges >= c.BurstCharges || s.HarmonicReadySeconds >= c.BurstMaxHoldSeconds || s.Player.Has(Statuses.Bravery) || s.Player.Has(Statuses.Fantasia) || s.Enemies.Any(a => !a.IsObjective && TargetSelector.Allowed(a, c, skill) && a.Distance <= 25 && a.EffectiveHp <= 12000)) return null; return $"等待爆发增益 / {c.BurstCharges} 充能(最多 {c.BurstMaxHoldSeconds:F1}s)"; } public static int AoeCount(CombatSnapshot s, Settings c, Skill skill, Actor center, AoeShape shape) { var origin = new Vector2(s.Player.Position.X, s.Player.Position.Z); var aim = new Vector2(center.Position.X, center.Position.Z); var delta = aim - origin; var direction = delta.LengthSquared() > .001f ? Vector2.Normalize(delta) : Vector2.Zero; return s.Enemies.Count(a => { if (!TargetSelector.Allowed(a, c, skill) || Math.Abs(a.Position.Y - center.Position.Y) > 5) return false; var point = new Vector2(a.Position.X, a.Position.Z); if (!shape.Line) return Vector2.Distance(point, aim) <= shape.Radius + a.HitboxRadius; if (direction == Vector2.Zero) return a.Id == center.Id; var along = Vector2.Dot(point - origin, direction); return along >= 0 && along <= shape.Radius + a.HitboxRadius && Math.Abs(direction.X * (point.Y - origin.Y) - direction.Y * (point.X - origin.X)) <= shape.HalfWidth + a.HitboxRadius; }); } // Shared by execution and diagnostics: a skill not visited due to an earlier return still gets a current policy reason. public static string? PolicyReason(Skill skill, CombatSnapshot s, Settings c) { if (!c.Enabled(skill)) return "技能已关闭"; if (s.ManualControl) return "手动接管:暂停自动动作"; if (!s.Player.Alive || s.GuardPending || s.Player.Has(Statuses.Guard)) return "死亡 / 自身防御,暂停自动动作"; if (!c.AutoAttack && (Actions.IsHostile(skill) || skill is Skill.FinalFantasia or Skill.Bravery or Skill.Dervish)) return "自动攻击已关闭"; var self = s.Player; var status = skill switch { Skill.PitchPerfect => Statuses.Repertoire, Skill.BlastArrow => Statuses.BlastReady, Skill.EncoreOfLight => Statuses.EncoreReady, Skill.EagleEyeShot => Statuses.RoleEagle, Skill.Bravery => Statuses.RoleBravery, Skill.Dervish => Statuses.RoleDervish, _ => 0u }; if (status != 0 && !self.Has(status)) return $"缺少前置状态 / 职能装备 ({status})"; if (skill == Skill.PowerfulShot && self.Has(Statuses.Repertoire) || skill == Skill.ApexArrow && self.Has(Statuses.BlastReady)) return "技能已变换为后续技能"; if (skill is Skill.RepellingShot or Skill.SilentNocturne && self.Has(Statuses.Repertoire)) return "已有完美音调,避免覆盖"; if (skill is Skill.Guard or Skill.Recuperate or Skill.Elixir && s.AliveSeconds < 5) return "复活后 5 秒保护"; if (skill == Skill.Guard && (!s.InCombat || !GuardNeeded(s, c))) return "未达到防御阈值 / 集火条件"; if (skill == Skill.Recuperate && (long)self.MaxHp - self.Hp < HealDeficit(s, c)) return "缺失 HP 未达到自疗阈值"; if (skill == Skill.Purify && (s.CleansePending || !Statuses.Controls.Any(id => self.Has(id) && !c.DisabledPurifyStatuses.Contains(id)))) return "无启用的净化控制 / 正在等待解控"; if (skill == Skill.WardensPaean && (s.CleansePending || !Statuses.Controls.Any(self.Has) && (!c.HelpParty || !s.Party.Any(a => a.Alive && a.Targetable && Statuses.Controls.Any(a.Has))))) return "无礼赞解控目标 / 正在等待解控"; if (skill == Skill.Elixir && (s.Moving || TargetSelector.HasNearbyThreat(s) || self.HpRatio * 100 > c.ElixirPercent && (s.MaxMp == 0 || (double)s.Mp / s.MaxMp * 100 > c.ElixirPercent))) return "补给条件不满足:资源 / 移动 / 周围威胁"; if (skill is Skill.Bravery or Skill.Dervish or Skill.FinalFantasia && (!s.InCombat || !s.Enemies.Any(a => a.Enemy && a.Alive))) return "进攻增益需要战斗和敌人"; if (skill == Skill.Bravery && self.Has(Statuses.Bravery) || skill == Skill.Dervish && self.Has(Statuses.Dervish)) return "增益已存在"; if (skill == Skill.FinalFantasia && (!s.LimitFull || self.Has(Statuses.Fantasia) || self.Has(Statuses.EncoreReady))) return "极限量谱未满 / 增益或安可尚未消耗"; if (skill == Skill.Sprint && (!s.Moving || self.Has(Statuses.Sprint))) return "未移动 / 冲刺已存在"; return HoldReason(skill, s, c); } } public sealed class HealthPressure { private readonly Queue<(double Time, uint Loss)> losses = new(); private uint? lastHp; public double Observe(double now, uint hp, uint maxHp) { if (lastHp is { } prior && prior > hp) losses.Enqueue((now, prior - hp)); lastHp = hp; while (losses.TryPeek(out var item) && now - item.Time > 2) losses.Dequeue(); return maxHp == 0 ? 0 : losses.Sum(x => (double)x.Loss) / maxHp; } public void Reset() { losses.Clear(); lastHp = null; } }