using System.Collections.Generic;
using System.Linq;
namespace Luneraria.Core.Tests
{
///
/// S1: the extended rules after Legion TD 2, Dota 2 and League of Legends (MatchOptions.Extended).
/// Champion income, barracks, exhaustion and buyback, Mondsegen, the bot rotation with later
/// heroes, and that none of it acts under the reference rules.
///
public sealed class S1RulesSuite : CheckSuite
{
public override string Name { get { return "s1"; } }
protected override void RunChecks()
{
TestReferenceUntouched();
TestChampionIncome();
TestBarracks();
TestExhaustionAndBuyback();
TestBlessing();
TestBots();
}
static LuneMatch NewMatch(bool extended, int seed = 41)
{
var state = new LuneMatch();
state.Setup(new MatchOptions { Seed = seed, HumanSlot = -1, Extended = extended,
Bunds = new List { "glutpakt", "wurzelbund", "glutpakt", "wurzelbund", "glutpakt", "wurzelbund" } });
return state;
}
void TestReferenceUntouched()
{
LuneMatch state = NewMatch(false);
Check(!state.Extended, "default options keep the reference rules");
state.Players[0].Draft.Gold += 1000;
state.Submit(0, Command.ChampionBook("ch_schutz", "top"));
state.Submit(0, Command.HeroBuy("held_ringborke"));
state.BeginCombat();
Check(state.Players[0].IncomeBonus == 0, "reference: a champion gives no income");
foreach (LuneUnit unit in state.Combat.Units.Where(u => u.Kind == "hero"))
unit.Hp = 0.0;
state.FinishCombat();
Check(!state.Players[0].Hero.Exhausted, "reference: a fallen hero is not exhausted");
Check(!state.Submit(0, Command.Buyback()).Ok, "reference: no buyback");
var setup = new LuneMatch();
setup.Setup(new MatchOptions { Seed = 3 });
Check(!setup.Extended, "MatchOptions.Extended defaults to false");
}
void TestChampionIncome()
{
LuneMatch state = NewMatch(true);
state.Players[0].Draft.Gold += 1000;
Check(state.Submit(0, Command.ChampionBook("ch_schutz", "top")).Ok, "book a champion");
Check(state.Players[0].IncomeBonus == 0, "income rises only when the booking is committed");
state.Submit(0, Command.ChampionCancel(state.Players[0].Draft.NewBookings[0].Uid));
state.Submit(0, Command.ChampionBook("ch_belagerung", "mid"));
state.BeginCombat();
Check(state.Players[0].IncomeBonus == 180 / 12, "Keilbrecher adds 15 income for good (" + state.Players[0].IncomeBonus + ")");
int before = state.Players[0].Gold;
int beforeOther = state.Players[1].Gold;
state.FinishCombat();
int gain = state.Players[0].Gold - before;
int gainOther = state.Players[1].Gold - beforeOther;
Check(gain - state.LastSettlement.Gains[0] == state.LastSettlement.Income + 15, "settlement pays round income plus the champion income");
Check(gainOther - state.LastSettlement.Gains[1] == state.LastSettlement.Income, "other seats get the plain income");
}
void TestBarracks()
{
LuneMatch state = NewMatch(true);
Check(!state.BarracksBroken(0, "mid"), "barracks intact at the start");
state.BuildingHp["rot_mid_inner"] = 0.0;
Check(state.BarracksBroken(0, "mid") && !state.BarracksBroken(1, "mid") && !state.BarracksBroken(0, "top"), "Blau's mid waves are reinforced once Rot's inner mid tower fell");
state.BeginCombat();
List blueMid = state.Combat.Units.Where(u => u.Kind == "monster" && u.Team == 0 && u.Lane == "mid").ToList();
List blueTop = state.Combat.Units.Where(u => u.Kind == "monster" && u.Team == 0 && u.Lane == "top").ToList();
List redMid = state.Combat.Units.Where(u => u.Kind == "monster" && u.Team == 1 && u.Lane == "mid").ToList();
Check(blueMid.Count == blueTop.Count + 1 && redMid.Count == blueTop.Count, "one extra monster on the broken lane only");
Check(GdMath.IsEqualApprox(blueMid.Last().MaxHp, blueTop.Last().MaxHp * 1.25), "reinforced monsters have 125 % health");
LuneMatch reference = NewMatch(false);
reference.BuildingHp["rot_mid_inner"] = 0.0;
reference.BeginCombat();
Check(reference.Combat.Units.Count(u => u.Kind == "monster" && u.Team == 0 && u.Lane == "mid") == blueTop.Count, "reference: no barracks effect");
}
void TestExhaustionAndBuyback()
{
LuneMatch state = NewMatch(true);
state.Players[1].Draft.Gold += 2000;
state.Submit(1, Command.HeroBuy("held_sylvara"));
Check(!state.Submit(1, Command.Buyback()).Ok, "no buyback for a fresh hero");
state.BeginCombat();
LuneUnit hero = state.Combat.Units.First(u => u.Kind == "hero" && u.OwnerSlot == 1);
hero.Hp = 0.0;
state.FinishCombat();
HeroState committed = state.Players[1].Hero;
Check(committed.Exhausted, "a hero that fell is exhausted");
state.BeginCombat();
hero = state.Combat.Units.First(u => u.Kind == "hero" && u.OwnerSlot == 1);
Check(GdMath.IsEqualApprox(hero.Hp, hero.MaxHp * 0.6), "an exhausted hero starts with 60 % health");
state.FinishCombat();
Check(!state.Players[1].Hero.Exhausted || !state.Combat.Units.First(u => u.Kind == "hero" && u.OwnerSlot == 1).Alive, "surviving clears the exhaustion");
state.Players[1].Hero.Exhausted = true;
state.Players[1].Draft = state.Snapshot(state.Players[1]);
int level = LuneContent.LevelFor(state.Players[1].Hero.Xp);
int cost = 60 + 20 * level;
int gold = state.GoldOf(1);
Check(state.Submit(1, Command.Buyback()).Ok && state.GoldOf(1) == gold - cost, "buyback costs 60 + 20 × level (" + cost + ")");
Check(state.Undo(1).Ok && state.GoldOf(1) == gold && state.Players[1].Draft.Hero.Exhausted, "buyback can be undone");
state.Submit(1, Command.Buyback());
state.BeginCombat();
hero = state.Combat.Units.First(u => u.Kind == "hero" && u.OwnerSlot == 1);
Check(hero.Hp == hero.MaxHp, "a bought back hero starts at full health");
state.FinishCombat();
}
void TestBlessing()
{
LuneMatch state = NewMatch(true);
state.Players[0].Draft.Gold += 1000;
state.Submit(0, Command.Buy("g01", "top"));
state.Submit(0, Command.HeroBuy("held_funkenlanze"));
state.BeginCombat();
state.Combat.GrottoOutcome = "success";
state.Combat.GrottoClaimTeam = 0;
state.SettleBoss();
Check(state.Boss.Blessing[0] == 1 && state.Boss.Blessing[1] == 0, "a grotto victory blesses the winning team");
state.FinishCombat();
state.Boss.Blessing[0] = 2;
state.BeginCombat();
LuneUnit troop = state.Combat.Units.First(u => u.Kind == "troop" && u.OwnerSlot == 0);
Check(GdMath.IsEqualApprox(troop.MaxHp, DataCatalog.GetUnit("g01").MaxHp * 1.08), "two stacks: troops have 108 % health");
LuneUnit enemy = state.Combat.Units.FirstOrDefault(u => u.Kind == "troop" && u.Team == 1);
Check(enemy == null || GdMath.IsEqualApprox(enemy.MaxHp, DataCatalog.GetUnit(enemy.Def.Id).MaxHp), "the other team is not blessed");
state.FinishCombat();
state.Boss.Blessing[0] = 4;
state.BeginCombat();
LuneUnit hero = state.Combat.Units.First(u => u.Kind == "hero" && u.OwnerSlot == 0);
Check(GdMath.IsEqualApprox(hero.Def.Regen, 0.01), "Mondseele: heroes regenerate 1 % per second");
state.FinishCombat();
state.Combat.GrottoOutcome = "success";
state.Combat.GrottoClaimTeam = 0;
state.Boss.SettledRound = 0;
state.Boss.Claim = null;
state.Boss.NextAvailableRound = 1;
state.SettleBoss();
Check(state.Boss.Blessing[0] == 4, "Mondsegen stops at four stacks");
}
void TestBots()
{
bool sylvaraPicked = false;
for (int seed = 1; seed <= 20 && !sylvaraPicked; seed++)
{
LuneMatch probe = NewMatch(true, seed);
for (int slot = 0; slot < 6; slot++)
sylvaraPicked |= LuneBot.ProfileFor(probe, slot) == "assassine";
}
Check(sylvaraPicked, "extended rules: the bot rotation includes Sylvara's profile");
string first = Play(17, out LuneMatch played);
Check(first == Play(17, out LuneMatch _), "extended bot matches are deterministic");
Check(played.Phase == Phase.Over, "an extended bot match ends by an Ancient (" + first.Split('|')[0] + ")");
Check(played.Players.Any(p => p.IncomeBonus > 0), "bots earn champion income");
Note("extended seed 17: " + first.Split('|')[0] + ", income bonus " + string.Join(",", played.Players.Select(p => p.IncomeBonus)) + ", blessing " + played.Boss.Blessing[0] + "/" + played.Boss.Blessing[1]);
}
static string Play(int seed, out LuneMatch match)
{
match = NewMatch(true, seed);
while (match.Phase != Phase.Over && match.RoundNumber <= 80)
{
for (int slot = 0; slot < 6; slot++)
LuneBot.Plan(match, slot);
match.BeginCombat();
match.FinishCombat();
}
return match.RoundNumber + "/" + match.Winner + "|" + string.Join(",", match.Players.Select(p => p.Gold + ":" + p.Earned + ":" + p.IncomeBonus))
+ "|" + string.Join(",", match.BuildingHp.Values.Select(v => v.ToString("R", System.Globalization.CultureInfo.InvariantCulture)));
}
}
}