Files
BardFrontline/Core/Strategy.cs
T

90 lines
6.5 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.
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; }
}