Files

127 lines
8.4 KiB
C#

using BardFrontline.Core;
using System.Text.Json;
static class PressureTests
{
static Actor Self(params uint[] states) => new(1, "self", 60000, 60000, 0, false, true, false, 0, [.. states]);
static Actor Enemy(ulong id = 10, uint hp = 30000) => new(id, "enemy", hp, 60000, 18, true, true, false, 0, []);
static CombatSnapshot Snap(params uint[] states) => new(Self(states), 10000, 10000, true, false, 10, false, [Enemy()], [], 0);
static Settings Config() => new() { Burst = BurstStrategy.Pressure };
static ActionDecision? Decide(CombatSnapshot s, Settings c, params Skill[] ready) => new Rotation().Decide(s, c, new Fake(ready));
static void Is(Skill skill, ActionDecision? d) => Suite.Equal(skill, d?.Skill);
public static void Run(Suite suite)
{
suite.Run("Pressure opt-in preserves serialized modes and original default", () =>
{
Suite.Equal(BurstStrategy.Normal, new Settings().Burst);
foreach (var mode in Enum.GetValues<BurstStrategy>())
{
var c = JsonSerializer.Deserialize<Settings>(JsonSerializer.Serialize(new Settings { Burst = mode }))!;
c.Normalize(); Suite.Equal(mode, c.Burst);
}
Suite.Equal(0, (int)BurstStrategy.Normal); Suite.Equal(1, (int)BurstStrategy.AlignBuffs);
Suite.Equal(2, (int)BurstStrategy.ManualWindow);
});
suite.Run("Pressure releases regular Eagle before Harmonic and filler without changing default", () =>
{
var s = Snap(Statuses.March, Statuses.RoleEagle);
Skill[] ready = [Skill.HarmonicArrow, Skill.PowerfulShot, Skill.EagleEyeShot];
Is(Skill.HarmonicArrow, Decide(s, new(), ready));
Is(Skill.EagleEyeShot, Decide(s, Config(), ready));
});
suite.Run("Pressure uses silence before filler and still avoids overwriting Repertoire", () =>
{
Is(Skill.PowerfulShot, Decide(Snap(), new(), Skill.PowerfulShot, Skill.SilentNocturne));
Is(Skill.SilentNocturne, Decide(Snap(), Config(), Skill.PowerfulShot, Skill.SilentNocturne));
Is(Skill.HarmonicArrow, Decide(Snap(Statuses.Repertoire), Config(), Skill.SilentNocturne, Skill.HarmonicArrow));
});
suite.Run("Pressure consumes ready procs before multiple Harmonic charges and normal Eagle", () =>
{
foreach (var (status, skill) in new[] { (Statuses.Repertoire, Skill.PitchPerfect), (Statuses.BlastReady, Skill.BlastArrow) })
{
var s = Snap(Statuses.March, status, Statuses.RoleEagle) with { HarmonicCharges = 4 };
Is(skill, Decide(s, Config(), skill, Skill.HarmonicArrow, Skill.EagleEyeShot));
Is(Skill.HarmonicArrow, Decide(s, new(), skill, Skill.HarmonicArrow, Skill.EagleEyeShot));
}
});
suite.Run("Pressure preserves opener buffs Encore and Eagle finisher precedence", () =>
{
Is(Skill.ApexArrow, Decide(Snap(Statuses.RoleEagle), Config(), Skill.ApexArrow, Skill.EagleEyeShot, Skill.HarmonicArrow));
Is(Skill.Bravery, Decide(Snap(Statuses.RoleBravery, Statuses.RoleEagle), Config(), Skill.Bravery, Skill.EagleEyeShot));
Is(Skill.FinalFantasia, Decide(Snap(Statuses.RoleEagle) with { LimitFull = true }, Config(), Skill.FinalFantasia, Skill.EagleEyeShot));
Is(Skill.EncoreOfLight, Decide(Snap(Statuses.EncoreReady, Statuses.RoleEagle), Config(), Skill.EncoreOfLight, Skill.EagleEyeShot));
Is(Skill.EagleEyeShot, Decide(Snap(Statuses.EncoreReady, Statuses.RoleEagle) with { Enemies = [Enemy(hp: 9000)] },
Config(), Skill.EncoreOfLight, Skill.EagleEyeShot));
});
suite.Run("Pressure respects strict Eagle reservation and unavailable ability fallback", () =>
{
var s = Snap(Statuses.RoleEagle);
var c = Config(); c.EagleStrategy = EagleStrategy.FinisherOnly;
Is(Skill.PowerfulShot, Decide(s, c, Skill.PowerfulShot, Skill.EagleEyeShot));
c = Config(); c.DisabledSkills = [Skill.EagleEyeShot, Skill.SilentNocturne];
Is(Skill.PowerfulShot, Decide(s, c, Skill.PowerfulShot, Skill.EagleEyeShot, Skill.SilentNocturne));
var f = new Fake(Skill.EagleEyeShot, Skill.PowerfulShot); f.Charges[Skill.EagleEyeShot] = 0;
Is(Skill.PowerfulShot, new Rotation().Decide(s, Config(), f));
});
suite.Run("Pressure never holds charges or buffs for alignment", () =>
{
var s = Snap() with { HarmonicCharges = 1, HarmonicReadySeconds = 0 };
foreach (var skill in new[] { Skill.HarmonicArrow, Skill.Bravery, Skill.Dervish, Skill.FinalFantasia })
Suite.True(Strategy.HoldReason(skill, s, Config()) == null);
Is(Skill.HarmonicArrow, Decide(s, Config(), Skill.HarmonicArrow, Skill.PowerfulShot));
});
suite.Run("Pressure waives extra instant damage weave margin but excludes casts and backstep", () =>
{
var w = new WeaveTiming();
for (var i = 0; i < 3; i++) { w.Submitted(i * 2, false); w.Observe(i * 2 + 1.2, true, 0, false, false); }
foreach (var adaptive in new[] { true, false })
foreach (var skill in new[] { Skill.HarmonicArrow, Skill.PitchPerfect, Skill.BlastArrow, Skill.ApexArrow,
Skill.EagleEyeShot, Skill.SilentNocturne, Skill.Bravery, Skill.Dervish, Skill.FinalFantasia })
{
Suite.True(!w.Allows(.1f, skill, adaptive));
Suite.True(w.Allows(.1f, skill, adaptive, BurstStrategy.Pressure));
Suite.True(!w.Allows(.1f, skill, adaptive, BurstStrategy.Pressure, casting: true));
}
Suite.True(!w.Allows(.1f, Skill.RepellingShot, true, BurstStrategy.Pressure));
Suite.True(!w.Allows(.1f, Skill.PowerfulShot, true, BurstStrategy.Pressure));
});
suite.Run("Pressure retains survival Guard manual pause and attack switches", () =>
{
var s = Snap(Statuses.RoleEagle);
Is(Skill.Guard, Decide(s with { Player = Self(Statuses.RoleEagle) with { Hp = 8000 } },
Config(), Skill.Guard, Skill.EagleEyeShot));
Is(Skill.Recuperate, Decide(s with { Player = Self(Statuses.RoleEagle) with { Hp = 40000 } },
Config(), Skill.Recuperate, Skill.EagleEyeShot));
Suite.True(Decide(s with { ManualControl = true }, Config(), Skill.EagleEyeShot) == null);
Suite.True(Decide(s with { Player = Self(Statuses.RoleEagle, Statuses.Guard) }, Config(), Skill.EagleEyeShot) == null);
var c = Config(); c.AutoAttack = false;
Suite.True(Decide(s, c, Skill.EagleEyeShot, Skill.SilentNocturne) == null);
});
suite.Run("Pressure keeps player-first targeting and Eagle off objectives", () =>
{
var objective = Enemy(11, 1000) with { IsObjective = true };
var s = Snap(Statuses.RoleEagle) with { Enemies = [objective, Enemy()] };
Suite.Equal(10ul, Decide(s, Config(), Skill.EagleEyeShot, Skill.HarmonicArrow)!.TargetId);
Is(Skill.HarmonicArrow, Decide(s with { Enemies = [objective] }, Config(), Skill.EagleEyeShot, Skill.HarmonicArrow));
});
suite.Run("Pressure does not move backstep ahead of filler or override manual backstep", () =>
{
var s = Snap() with { Enemies = [Enemy() with { Distance = 8 }] };
Is(Skill.PowerfulShot, Decide(s, Config(), Skill.RepellingShot, Skill.PowerfulShot));
var c = Config(); c.ManualRepelling = true;
Suite.True(Decide(s, c, Skill.RepellingShot) == null);
});
suite.Run("Pressure burst falls back to filler after spending cooldowns and consuming proc", () =>
{
var s = Snap(Statuses.March, Statuses.RoleEagle);
Is(Skill.EagleEyeShot, Decide(s, Config(), Skill.EagleEyeShot, Skill.SilentNocturne, Skill.HarmonicArrow, Skill.PowerfulShot));
Is(Skill.SilentNocturne, Decide(s, Config(), Skill.SilentNocturne, Skill.HarmonicArrow, Skill.PowerfulShot));
Is(Skill.PitchPerfect, Decide(s with { Player = Self(Statuses.March, Statuses.RoleEagle, Statuses.Repertoire) },
Config(), Skill.PitchPerfect, Skill.HarmonicArrow));
Is(Skill.HarmonicArrow, Decide(s, Config(), Skill.HarmonicArrow, Skill.PowerfulShot));
Is(Skill.PowerfulShot, Decide(s, Config(), Skill.PowerfulShot));
});
}
}