champions-of-luneraria/Assets/Luneraria/Tests/Core/H2HeroSuite.cs
Martin Stickelbroeck cbff678290 H2A Brunhild: Kernregeln, Markierung, Betäubung, Bot-Profil, Prüfsuite
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>
2026-10-11 16:44:42 +02:00

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");
}
}
}