Brunhild (held_brunhild): Heldenjägerin (+25 % gegen Helden/Champions), Speerwurf markiert (+15 % erlittener Schaden), Walkürensturz (Sprung, 200 % in 3 m, 1 s Betäubung, Bosse immun, Walkürenschild 30 %), Talente, Auslöser, Skin-Eintrag Eiswalküre, Bot-Profil walkuere (nur erweitert). Neue Effektarten vulnerable und stun wirken nur durch Brunhild; Golden 0. Probe-Vergleich 50 % wie die übrigen Helden; h2 22/22. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
148 lines
8.1 KiB
C#
148 lines
8.1 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Luneraria.Core.Tests
|
|
{
|
|
/// <summary>
|
|
/// H2: Brunhild, the Valkyrie. Purchase and rules, hunter passive, spear with mark, the leap
|
|
/// with area damage and stun (bosses immune), talents, skin, bot profile; reference untouched.
|
|
/// </summary>
|
|
public sealed class H2HeroSuite : CheckSuite
|
|
{
|
|
public override string Name { get { return "h2"; } }
|
|
|
|
protected override void RunChecks()
|
|
{
|
|
TestPurchase();
|
|
TestSpear();
|
|
TestUltimate();
|
|
TestPassiveAndTalents();
|
|
TestBots();
|
|
}
|
|
|
|
static LuneCombat Arena(HeroState hero, out LuneUnit brunhild, params KeyValuePair<string, double>[] enemies)
|
|
{
|
|
var balance = LuneBalance.Default();
|
|
var combat = new LuneCombat(new LuneMap(balance), balance, 5);
|
|
UnitDef definition = LuneContent.HeroDefinition(hero);
|
|
definition.Role = "melee";
|
|
brunhild = combat.Spawn("hero", 0, "mid", definition, 40.0, 0.0, 0, "h0-1");
|
|
Vec2 forward = combat.Map.LaneForward(0, "mid", 40.0);
|
|
foreach (KeyValuePair<string, double> enemy in enemies)
|
|
{
|
|
bool isHero = enemy.Key == "held";
|
|
UnitDef def = isHero ? LuneContent.HeroDefinition(new HeroState { HeroId = "held_ringborke" }) : ContentData.Enemies[enemy.Key].Clone();
|
|
def.MaxHp = 50000.0;
|
|
def.Speed = 0.0;
|
|
LuneUnit unit = combat.Spawn(isHero ? "hero" : "monster", 1, "mid", def, 40.0, 0.0, isHero ? 3 : -1, isHero ? "h3-1" : "");
|
|
unit.Position = brunhild.Position + forward * enemy.Value;
|
|
unit.PreviousPosition = unit.Position;
|
|
}
|
|
return combat;
|
|
}
|
|
|
|
static KeyValuePair<string, double> At(string id, double distance) { return new KeyValuePair<string, double>(id, distance); }
|
|
|
|
static void Run(LuneCombat combat, double seconds)
|
|
{
|
|
int steps = (int)(seconds / combat.Balance.SimStep + 0.5);
|
|
for (int i = 0; i < steps && !combat.Finished; i++)
|
|
combat.Step();
|
|
}
|
|
|
|
void TestPurchase()
|
|
{
|
|
var state = new LuneMatch();
|
|
state.Setup(new MatchOptions { Seed = 71, HumanSlot = -1 });
|
|
state.Players[0].Draft.Gold += 1000;
|
|
Check(state.Submit(0, Command.HeroBuy("held_brunhild")).Ok, "Brunhild can be recruited for 260");
|
|
Check(LuneContent.UltimateRuleFor(state.Players[0].Draft.Hero) == "gruppe", "default rule Gruppe");
|
|
Check(state.Submit(0, Command.UltimateRule("held")).Ok && state.Submit(0, Command.HeroSkin("eiswalkuere")).Ok, "rule Heldenjagd and skin Eiswalküre");
|
|
Check(!state.Submit(0, Command.HeroSkin("blutmond")).Ok, "Sylvara's skin is not hers");
|
|
UnitDef definition = LuneContent.HeroDefinition(state.Players[0].Draft.Hero);
|
|
Check(definition.Id == "brunhild" && definition.Skin == "eiswalkuere" && definition.UltimateRule == "held" && GdMath.IsEqualApprox(definition.AttackRange, 2.0), "definition carries model, skin, rule and the spear's reach");
|
|
}
|
|
|
|
void TestSpear()
|
|
{
|
|
LuneUnit brunhild;
|
|
LuneCombat combat = Arena(new HeroState { HeroId = "held_brunhild", Ultimate = "notfall" }, out brunhild, At("e01", 1.5), At("e01", 4.0), At("e04", 6.5));
|
|
LuneUnit far = combat.Units.First(u => u.Def.Id == "e04");
|
|
Run(combat, LuneCombat.BrunhildFirstSpear - 0.1);
|
|
Check(brunhild.AbilitySerial == 0, "no spear before 3 s");
|
|
Run(combat, 0.2);
|
|
Check(brunhild.AbilitySerial == 1 && far.Effects.ContainsKey("valkyrie_mark"), "the first spear marks the farthest enemy in 7 m");
|
|
double raw = 100.0;
|
|
var probe = new LuneUnit { Def = far.Def };
|
|
Check(GdMath.IsEqualApprox(far.Mitigated(raw, 0), probe.Mitigated(raw, 0) * 1.15), "a marked enemy takes 15 % more damage");
|
|
}
|
|
|
|
void TestUltimate()
|
|
{
|
|
LuneUnit brunhild;
|
|
LuneCombat combat = Arena(new HeroState { HeroId = "held_brunhild", Ultimate = "held" }, out brunhild, At("e01", 1.5), At("held", 6.0), At("e01", 6.8), At("e07", 6.5));
|
|
LuneUnit hero = combat.Units.First(u => u.Kind == "hero" && u.Team == 1);
|
|
LuneUnit boss = combat.Units.First(u => u.Def.Id == "e07");
|
|
Run(combat, LuneContent.UltimateDelay - 0.1);
|
|
Check(brunhild.UltimateSerial == 0, "no ultimate before 2 s");
|
|
Run(combat, 0.1);
|
|
Check(brunhild.UltimateSerial == 1, "Heldenjagd fires with an enemy hero in 7 m");
|
|
Check(brunhild.Position.DistanceTo(hero.Position) < 1.5, "she leaps next to the hero");
|
|
Check(hero.Stunned() && !boss.Stunned(), "the hero is stunned, the boss is immune");
|
|
Check(hero.AttackSpeedFactor() == 0.0 && hero.MovementFactor() == 0.0, "a stunned unit neither attacks nor moves");
|
|
Run(combat, 1.1);
|
|
Check(!hero.Stunned(), "the stun ends after 1 s");
|
|
Run(combat, 8.0);
|
|
Check(brunhild.UltimateSerial == 1, "once per combat");
|
|
LuneUnit calm;
|
|
LuneCombat group = Arena(new HeroState { HeroId = "held_brunhild" }, out calm, At("e01", 3.0), At("e01", 3.5));
|
|
Run(group, 4.0);
|
|
Check(calm.UltimateSerial == 0, "Gruppe waits for three enemies at the target");
|
|
}
|
|
|
|
void TestPassiveAndTalents()
|
|
{
|
|
LuneUnit brunhild;
|
|
LuneCombat combat = Arena(new HeroState { HeroId = "held_brunhild", Ultimate = "notfall" }, out brunhild, At("held", 1.5));
|
|
LuneUnit hero = combat.Units.First(u => u.Kind == "hero" && u.Team == 1);
|
|
double before = hero.Hp;
|
|
int guard = 0;
|
|
while (brunhild.HitCount == 0 && guard++ < 200)
|
|
combat.Step();
|
|
double expected = hero.Mitigated(brunhild.Def.Damage * (1.0 + brunhild.DamageBonus()) * LuneCombat.BrunhildHunter, brunhild.Def.AttackType);
|
|
Check(GdMath.IsEqualApprox(before - hero.Hp, expected), "Heldenjägerin: +25 % against heroes");
|
|
UnitDef raven = LuneContent.HeroDefinition(new HeroState { HeroId = "held_brunhild", Xp = 1000, Talents = new List<string> { "speerregen", "rabenschwinge" } });
|
|
Check(GdMath.IsEqualApprox(raven.AttackSpeedBonus, 0.20), "Rabenschwinge: +20 % attack tempo");
|
|
LuneUnit rain;
|
|
LuneCombat spears = Arena(new HeroState { HeroId = "held_brunhild", Ultimate = "notfall", Talents = new List<string> { "speerregen", "goetterdaemmerung" } }, out rain,
|
|
At("e01", 4.0), At("e01", 5.0), At("e01", 6.0), At("e01", 1.0));
|
|
Run(spears, 3.1);
|
|
Check(spears.Units.Count(u => u.Effects.ContainsKey("valkyrie_mark")) == 3, "Speerregen: three marked enemies");
|
|
LuneUnit dusk;
|
|
LuneCombat stun = Arena(new HeroState { HeroId = "held_brunhild", Ultimate = "sofort", Talents = new List<string> { "speerregen", "goetterdaemmerung" } }, out dusk, At("e01", 2.0));
|
|
Run(stun, 2.05);
|
|
LuneUnit victim = stun.Units.First(u => u.Kind == "monster");
|
|
Run(stun, 1.5);
|
|
Check(victim.Stunned(), "Götterdämmerung: the stun lasts 2 s");
|
|
}
|
|
|
|
void TestBots()
|
|
{
|
|
bool picked = false;
|
|
for (int seed = 1; seed <= 16 && !picked; seed++)
|
|
{
|
|
var probe = new LuneMatch();
|
|
probe.Setup(new MatchOptions { Seed = seed, HumanSlot = -1, Extended = true });
|
|
for (int slot = 0; slot < 6; slot++)
|
|
picked |= LuneBot.ProfileFor(probe, slot) == "walkuere";
|
|
}
|
|
Check(picked, "extended bots pick Brunhild's profile");
|
|
var reference = new LuneMatch();
|
|
reference.Setup(new MatchOptions { Seed = 3, HumanSlot = -1 });
|
|
bool clean = true;
|
|
for (int slot = 0; slot < 6; slot++)
|
|
clean &= !ContentData.ExtraBotProfiles.ContainsKey(LuneBot.ProfileFor(reference, slot));
|
|
Check(clean, "reference rotation untouched");
|
|
}
|
|
}
|
|
}
|