Hinter MatchOptions.Extended: Glyphe als Teamwahl einer Lane (Türme 5 s unverwundbar, sobald einer unter 50 % fällt; 4 Runden Abklingzeit nur nach Auslösen), vier Rezepte (Bollwerk, Glutsturm, Quellherz, Splitterkrone), Ancient-Stufen 1-3 (150 x Stufe, Reparatur 1.000, Schuss 45/75/110), Mid-Bots jagen bei sicherer Lane die Grotte. Teampanel mit Glyphe und Ancient, Rezepte im Shop (TEILE FEHLEN), Glyphen-Ring und Ancient-Schüsse. Kern 566/566 (s2 35), EditMode 32/32, PlayMode 17/17, Verify 718/0, Selbsttest 30/30, Golden 0, Fingerabdruck unverändert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
162 lines
9.1 KiB
C#
162 lines
9.1 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Luneraria.Core.Tests
|
|
{
|
|
/// <summary>
|
|
/// S2: glyph (Dota 2), item recipes (Dota 2 / League of Legends), Ancient levels (Legion TD 2
|
|
/// king) and the bots' grotto hunt under the extended rules; none of it under the reference rules.
|
|
/// </summary>
|
|
public sealed class S2RulesSuite : CheckSuite
|
|
{
|
|
public override string Name { get { return "s2"; } }
|
|
|
|
protected override void RunChecks()
|
|
{
|
|
TestReference();
|
|
TestGlyph();
|
|
TestRecipes();
|
|
TestCore();
|
|
TestBots();
|
|
}
|
|
|
|
static LuneMatch NewMatch(bool extended, int seed = 51)
|
|
{
|
|
var state = new LuneMatch();
|
|
state.Setup(new MatchOptions { Seed = seed, HumanSlot = -1, Extended = extended,
|
|
Bunds = new List<string> { "glutpakt", "wurzelbund", "glutpakt", "wurzelbund", "glutpakt", "wurzelbund" } });
|
|
return state;
|
|
}
|
|
|
|
void TestReference()
|
|
{
|
|
LuneMatch state = NewMatch(false);
|
|
state.Players[0].Draft.Gold += 3000;
|
|
Check(!state.Submit(0, Command.Glyph("mid")).Ok, "reference: no glyph");
|
|
Check(!state.Submit(0, Command.CoreUpgrade()).Ok, "reference: no Ancient levels");
|
|
state.Submit(0, Command.HeroBuy("held_ringborke"));
|
|
state.Submit(0, Command.ItemBuy("basaltschild"));
|
|
state.Submit(0, Command.ItemBuy("rankenmantel"));
|
|
Check(!state.Submit(0, Command.ItemBuy("bollwerk")).Ok, "reference: no recipes");
|
|
state.BeginCombat();
|
|
Check(state.Combat.Buildings.Values.All(b => b.Kind != "core" || b.Def.Damage == 0.0), "reference: the Ancients do not shoot");
|
|
}
|
|
|
|
void TestGlyph()
|
|
{
|
|
LuneMatch state = NewMatch(true);
|
|
Check(!state.Submit(0, Command.Glyph("river")).Ok, "unknown lane rejected");
|
|
Check(state.Submit(1, Command.Glyph("mid")).Ok && state.Players[1].Draft.GlyphLane == "mid", "a player holds the team glyph for a lane");
|
|
state.Submit(0, Command.Glyph("top"));
|
|
state.BeginCombat();
|
|
Check(state.Combat.GlyphLane[0] == "top", "the first seat in slot order decides the team glyph");
|
|
LuneUnit tower = state.Combat.Buildings["blau_top_outer"];
|
|
tower.Hp = tower.MaxHp * 0.4;
|
|
state.Combat.Step();
|
|
Check(state.Combat.GlyphUsed[0] && state.Combat.Glyphed(tower), "the glyph fires once a tower of the lane is below half health");
|
|
double before = tower.Hp;
|
|
var gates = state.Combat.AliveMap();
|
|
state.Combat.QueueDamage(tower, 500.0, gates);
|
|
Check(!state.Combat.HasPendingDamage(tower), "glyphed towers take no damage");
|
|
Check(!state.Combat.Glyphed(state.Combat.Buildings["blau_mid_outer"]), "other lanes are not protected");
|
|
for (int i = 0; i < 120; i++)
|
|
state.Combat.Step();
|
|
Check(!state.Combat.Glyphed(tower), "the glyph ends after 5 s");
|
|
state.FinishCombat();
|
|
Check(state.Boss.GlyphReady[0] == state.RoundNumber - 1 + 1 + 4, "a fired glyph returns after 4 rounds (" + state.Boss.GlyphReady[0] + ")");
|
|
Check(!state.Submit(0, Command.Glyph("top")).Ok, "on cooldown the glyph cannot be held");
|
|
Check(state.Boss.GlyphReady[1] == 1, "the other team's glyph is untouched");
|
|
LuneMatch idle = NewMatch(true);
|
|
idle.Submit(2, Command.Glyph("bot"));
|
|
idle.BeginCombat();
|
|
idle.FinishCombat();
|
|
Check(idle.Combat.GlyphUsed[0] || idle.Boss.GlyphReady[0] == 1, "a glyph that never fired costs no cooldown");
|
|
}
|
|
|
|
void TestRecipes()
|
|
{
|
|
LuneMatch state = NewMatch(true);
|
|
state.Players[0].Draft.Gold += 3000;
|
|
state.Submit(0, Command.HeroBuy("held_ringborke"));
|
|
Check(!state.Submit(0, Command.ItemBuy("bollwerk")).Ok, "a recipe needs its parts");
|
|
state.Submit(0, Command.ItemBuy("basaltschild"));
|
|
state.Submit(0, Command.ItemBuy("rankenmantel"));
|
|
state.Submit(0, Command.ItemBuy("glutklinge"));
|
|
int gold = state.GoldOf(0);
|
|
Check(state.Submit(0, Command.ItemBuy("bollwerk")).Ok && state.GoldOf(0) == gold - 120, "combining costs the fee of 120");
|
|
HeroState hero = state.Players[0].Draft.Hero;
|
|
Check(hero.Items.SequenceEqual(new[] { "bollwerk", "glutklinge" }), "parts become the recipe item, a slot is free");
|
|
UnitDef definition = LuneContent.HeroDefinition(hero);
|
|
Check(GdMath.IsEqualApprox(definition.MaxHp, 2200.0 + 600.0) && GdMath.IsEqualApprox(definition.ReductionBonus, 0.15), "Bollwerk: +600 health, +15 % reduction");
|
|
Check(state.Undo(0).Ok && state.Players[0].Draft.Hero.Items.Count == 3, "combining can be undone");
|
|
state.Submit(0, Command.ItemBuy("bollwerk"));
|
|
Check(!state.Submit(0, Command.ItemBuy("splitterkrone")).Ok, "Splitterkrone needs its parts and level 5");
|
|
state.BeginCombat();
|
|
state.FinishCombat();
|
|
int sellGold = state.GoldOf(0);
|
|
Check(state.Submit(0, Command.ItemSell("bollwerk")).Ok && state.GoldOf(0) == sellGold + 250, "a recipe item sells for half its total (250)");
|
|
}
|
|
|
|
void TestCore()
|
|
{
|
|
LuneMatch state = NewMatch(true);
|
|
state.Players[0].Draft.Gold += 3000;
|
|
state.Players[1].Draft.Gold += 3000;
|
|
state.BuildingHp["blau_ancient"] = 5000.0;
|
|
int gold = state.GoldOf(0);
|
|
Check(state.Submit(0, Command.CoreUpgrade()).Ok && state.GoldOf(0) == gold - 150, "the first Ancient level costs 150");
|
|
Check(state.Submit(0, Command.CoreUpgrade()).Ok && state.GoldOf(0) == gold - 450, "the second costs 300");
|
|
Check(state.Submit(1, Command.CoreUpgrade()).Ok && state.Submit(1, Command.CoreUpgrade()).Ok, "a teammate buys too (priced by the committed level)");
|
|
state.BeginCombat();
|
|
Check(state.Boss.CoreLevel[0] == 3 && state.Boss.CoreLevel[1] == 0, "levels are capped at 3 for the team");
|
|
Check(state.Players[1].Gold > 0, "the teammate's excess level is refunded");
|
|
LuneUnit core = state.Combat.Buildings["blau_ancient"];
|
|
Check(GdMath.IsEqualApprox(core.Def.Damage, 110.0) && GdMath.IsEqualApprox(core.Def.AttackRange, 7.5), "level 3: the Ancient shoots 110 in 7.5 m");
|
|
Check(GdMath.IsEqualApprox(core.Hp, 8000.0), "each level repaired 1,000 (5,000 → 8,000)");
|
|
Check(!state.Submit(0, Command.CoreUpgrade()).Ok, "no commands in combat");
|
|
state.FinishCombat();
|
|
Check(!state.Submit(0, Command.CoreUpgrade()).Ok, "no level above 3");
|
|
}
|
|
|
|
void TestBots()
|
|
{
|
|
int hunts = 0;
|
|
int blessings = 0;
|
|
int combines = 0;
|
|
int glyphs = 0;
|
|
int cores = 0;
|
|
string first = "";
|
|
for (int seed = 1; seed <= 12; seed++)
|
|
{
|
|
LuneMatch match = NewMatch(true, seed);
|
|
while (match.Phase != Phase.Over && match.RoundNumber <= 80)
|
|
{
|
|
for (int slot = 0; slot < 6; slot++)
|
|
LuneBot.Plan(match, slot);
|
|
if (match.Players.Any(p => p.Draft.Roster.Any(r => r.Lane == LuneMatch.Grotto)))
|
|
hunts++;
|
|
match.BeginCombat();
|
|
match.FinishCombat();
|
|
glyphs += match.Combat.GlyphUsed.Count(used => used);
|
|
}
|
|
blessings += match.Boss.Blessing[0] + match.Boss.Blessing[1];
|
|
cores += match.Boss.CoreLevel[0] + match.Boss.CoreLevel[1];
|
|
combines += match.Players.Count(p => p.Hero != null && p.Hero.Items.Any(id => ContentData.Items[id].IsRecipe));
|
|
if (seed == 1)
|
|
first = match.RoundNumber + "/" + match.Winner;
|
|
}
|
|
Check(hunts > 0 && blessings > 0, "extended bots go to the grotto and win Mondsegen (" + hunts + " expeditions, " + blessings + " stacks in 12 matches)");
|
|
// Bots recruit heroes late (reference bot behaviour), so recipes rarely come up in whole
|
|
// matches; a prepared planning shows that a bot forges one when it can.
|
|
LuneMatch forge = NewMatch(true, 3);
|
|
Player bot = forge.Players[0];
|
|
bot.Hero = new HeroState { Uid = "h0-9", Seq = 9, HeroId = "held_ringborke", Lane = bot.Lane, Xp = 300, Invested = 260, Items = new List<string> { "basaltschild", "rankenmantel" } };
|
|
bot.Gold = 1200;
|
|
bot.Draft = forge.Snapshot(bot);
|
|
LuneBot.Plan(forge, 0);
|
|
Check(bot.Draft.Hero.Items.Contains("bollwerk"), "a bot forges Bollwerk from its parts (" + string.Join("+", bot.Draft.Hero.Items) + ")");
|
|
Check(glyphs > 0, "extended bots fire glyphs (" + glyphs + ")");
|
|
Note("12 extended matches: expeditions " + hunts + ", Mondsegen " + blessings + ", recipe heroes " + combines + ", glyphs " + glyphs + ", Ancient levels " + cores + "; seed 1 " + first);
|
|
}
|
|
}
|
|
}
|