using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Luneraria.Core.Tests
{
///
/// Compares every rule table and the computed map of the C# core with the export of the
/// Godot reference (Golden/content.json, tools/godot-golden/content_export.gd): units,
/// monsters, sends, heroes, talents, items, champions, bot profiles (order), balance
/// defaults, lane polylines and building positions. Tables must match exactly; the lane
/// geometry is computed and must match within 1e-5 m (the largest deviation is noted).
///
public sealed class ContentSuite : CheckSuite
{
public override string Name { get { return "content"; } }
const double GeometryTolerance = 0.00001;
protected override void RunChecks()
{
var data = MiniJson.Obj(MiniJson.Parse(File.ReadAllText(Path.Combine(GoldenDirectory, "content.json"))));
CompareUnits("units", MiniJson.Obj(data["units"]), ContentData.Units);
CompareUnits("enemies", MiniJson.Obj(data["enemies"]), ContentData.Enemies);
CompareUnits("sends", MiniJson.Obj(data["sends"]), ContentData.Sends);
CompareHeroes(MiniJson.Obj(data["heroes"]));
CompareItems(MiniJson.Obj(data["items"]));
CompareChampions(MiniJson.Obj(data["champions"]));
CompareTalents(MiniJson.Obj(data["talents"]));
CompareConstants(data);
CompareProfiles(MiniJson.Obj(data["bot_profiles"]));
CompareBalance(MiniJson.Obj(data["balance"]));
CompareMap(MiniJson.Obj(data["lanes"]), MiniJson.Arr(data["buildings"]));
}
void CompareUnits(string label, OrderedMap expected, OrderedMap actual)
{
Check(expected.Keys.SequenceEqual(actual.Keys), label + ": same ids in the same order");
foreach (KeyValuePair pair in expected)
{
var entry = MiniJson.Obj(pair.Value);
UnitDef unit;
if (!actual.TryGetValue(pair.Key, out unit))
{
Check(false, label + ": missing " + pair.Key);
continue;
}
var problems = new List();
Compare(problems, entry, "id", unit.Id);
Compare(problems, entry, "display_name", unit.DisplayName);
Compare(problems, entry, "cost", unit.Cost);
Compare(problems, entry, "upgrade_id", unit.UpgradeId);
Compare(problems, entry, "max_hp", unit.MaxHp);
Compare(problems, entry, "damage", unit.Damage);
Compare(problems, entry, "attack_type", unit.AttackType);
Compare(problems, entry, "armor_type", unit.ArmorType);
Compare(problems, entry, "attack_range", unit.AttackRange);
Compare(problems, entry, "speed", unit.Speed);
Compare(problems, entry, "ability", unit.Ability);
Compare(problems, entry, "leak_value", unit.LeakValue);
Compare(problems, entry, "bounty", unit.Bounty);
Compare(problems, entry, "ability_description", unit.AbilityDescription);
Compare(problems, entry, "is_boss", unit.IsBoss);
Compare(problems, entry, "income_gain", unit.IncomeGain);
Check(problems.Count == 0, label + " " + pair.Key + " matches the reference " + string.Join(", ", problems));
}
}
static void Compare(List problems, OrderedMap entry, string key, object actual)
{
object expected;
if (!entry.TryGetValue(key, out expected))
return;
bool same;
if (actual is double d)
same = MiniJson.Num(expected) == d;
else if (actual is int i)
same = MiniJson.Int(expected) == i;
else if (actual is bool b)
same = expected is bool eb && eb == b;
else
same = MiniJson.Str(expected) == MiniJson.Str(actual);
if (!same)
problems.Add(key + ": " + MiniJson.Str(expected) + " != " + MiniJson.Str(actual));
}
void CompareHeroes(OrderedMap expected)
{
// Heroes added after the port (H1) follow the reference heroes; those must match exactly.
Check(expected.Keys.SequenceEqual(ContentData.ReferenceHeroes) && ContentData.Heroes.Keys.Take(ContentData.ReferenceHeroes.Length).SequenceEqual(ContentData.ReferenceHeroes),
"heroes: same reference ids in the same order, later heroes after them");
Check(ContentData.Heroes.Keys.Skip(ContentData.ReferenceHeroes.Length).SequenceEqual(new[] { "held_sylvara" }), "heroes: exactly the documented later heroes (H1: held_sylvara)");
foreach (KeyValuePair pair in expected)
{
var entry = MiniJson.Obj(pair.Value);
HeroSpec hero = ContentData.Heroes[pair.Key];
var problems = new List();
Compare(problems, entry, "name", hero.Name);
Compare(problems, entry, "title", hero.Title);
Compare(problems, entry, "model", hero.Model);
Compare(problems, entry, "cost", hero.Cost);
Compare(problems, entry, "role", hero.RoleText);
Compare(problems, entry, "summary", hero.Summary);
var baseDef = MiniJson.Obj(entry["def"]);
Compare(problems, baseDef, "max_hp", hero.Base.MaxHp);
Compare(problems, baseDef, "damage", hero.Base.Damage);
Compare(problems, baseDef, "attack_type", hero.Base.AttackType);
Compare(problems, baseDef, "armor_type", hero.Base.ArmorType);
Compare(problems, baseDef, "attack_range", hero.Base.AttackRange);
Compare(problems, baseDef, "speed", hero.Base.Speed);
Compare(problems, baseDef, "ability", hero.Base.Ability);
Check(problems.Count == 0, "hero " + pair.Key + " matches " + string.Join(", ", problems));
}
}
void CompareItems(OrderedMap expected)
{
Check(expected.Keys.SequenceEqual(ContentData.Items.Keys), "items: same ids in the same order");
foreach (KeyValuePair pair in expected)
{
var entry = MiniJson.Obj(pair.Value);
ItemSpec item = ContentData.Items[pair.Key];
var problems = new List();
Compare(problems, entry, "name", item.Name);
Compare(problems, entry, "cost", item.Cost);
Compare(problems, entry, "text", item.Text);
Compare(problems, entry, "hp_flat", item.HpFlat);
Compare(problems, entry, "damage", item.Damage);
Compare(problems, entry, "reduction", item.Reduction);
Compare(problems, entry, "attack_speed", item.AttackSpeed);
Compare(problems, entry, "regen", item.Regen);
Compare(problems, entry, "splash", item.Splash);
Compare(problems, entry, "min_level", item.MinLevel);
Check(problems.Count == 0, "item " + pair.Key + " matches " + string.Join(", ", problems));
}
}
void CompareChampions(OrderedMap expected)
{
Check(expected.Keys.SequenceEqual(ContentData.Champions.Keys), "champions: same ids in the same order");
foreach (KeyValuePair pair in expected)
{
var entry = MiniJson.Obj(pair.Value);
ChampionSpec spec = ContentData.Champions[pair.Key];
var problems = new List();
Compare(problems, entry, "name", spec.Name);
Compare(problems, entry, "role", spec.RoleText);
Compare(problems, entry, "model", spec.Model);
Compare(problems, entry, "cost", spec.Cost);
Compare(problems, entry, "base", spec.Base);
Compare(problems, entry, "text", spec.Text);
Compare(problems, entry, "priority", spec.Priority);
Compare(problems, entry, "siege_factor", spec.SiegeFactor);
Compare(problems, entry, "view_scale", spec.ViewScale);
Check(problems.Count == 0, "champion " + pair.Key + " matches " + string.Join(", ", problems));
}
}
void CompareTalents(OrderedMap expected)
{
bool same = expected.Count == ContentData.Talents.Count - (ContentData.Heroes.Count - ContentData.ReferenceHeroes.Length);
foreach (KeyValuePair pair in expected)
{
TalentOption[][] tiers;
if (!ContentData.Talents.TryGetValue(pair.Key, out tiers))
{
same = false;
continue;
}
List