using System;
using System.Collections.Generic;
using System.Linq;
namespace Luneraria.Core.Tests
{
///
/// Runs the operations of Golden/scenarios.json against the C# core and records snapshots in
/// exactly the shape tools/godot-golden/golden_scenarios.gd writes for the Godot reference.
///
public sealed class GoldenReplay
{
public LuneMatch State;
public readonly List Snapshots = new List();
public readonly List Results = new List();
public void Apply(OrderedMap op)
{
switch (MiniJson.Str(op["op"]))
{
case "setup":
{
State = new LuneMatch();
var options = new MatchOptions { Seed = MiniJson.Int(op["seed"]), HumanSlot = op.ContainsKey("human") ? MiniJson.Int(op["human"]) : -1 };
if (op.ContainsKey("bunds"))
options.Bunds = MiniJson.Arr(op["bunds"]).Select(MiniJson.Str).ToList();
if (op.ContainsKey("profiles"))
options.Profiles = MiniJson.Arr(op["profiles"]).Select(MiniJson.Str).ToList();
State.Setup(options);
break;
}
case "building":
State.BuildingHp[MiniJson.Str(op["id"])] = MiniJson.Num(op["hp"]);
break;
case "buildings_full":
foreach (BuildingDef definition in State.Map.Buildings)
State.BuildingHp[definition.Id] = definition.MaxHp;
break;
case "round":
State.RoundNumber = MiniJson.Int(op["value"]);
break;
case "gold":
State.Players[MiniJson.Int(op["slot"])].Draft.Gold += MiniJson.Int(op["add"]);
break;
case "gold_all":
foreach (Player player in State.Players)
player.Draft.Gold += MiniJson.Int(op["add"]);
break;
case "cmd":
{
int slot = MiniJson.Int(op["slot"]);
Command command = Resolve(MiniJson.Obj(op["command"]));
CommandResult result = State.Submit(slot, command);
Results.Add(new List { "cmd", (long)slot, command.Type, result.Ok, result.Message });
break;
}
case "bots":
for (int slot = 0; slot < 6; slot++)
LuneBot.Plan(State, slot, op.ContainsKey("profile") ? MiniJson.Str(op["profile"]) : "");
break;
case "begin":
Results.Add(new List { "begin", -1L, "", State.BeginCombat(), "" });
break;
case "steps":
for (int i = 0; i < MiniJson.Int(op["n"]); i++)
{
if (State.Phase != Phase.Combat)
break;
State.StepCombat();
}
break;
case "finish":
State.FinishCombat();
break;
case "unit_set":
{
var select = MiniJson.Obj(op["select"]);
foreach (LuneUnit unit in State.Combat.Units)
{
if (select.ContainsKey("kind") && unit.Kind != MiniJson.Str(select["kind"]))
continue;
if (select.ContainsKey("zone") && unit.Zone != MiniJson.Str(select["zone"]))
continue;
if (select.ContainsKey("team") && unit.Team != MiniJson.Int(select["team"]))
continue;
if (op.ContainsKey("hp"))
unit.Hp = MiniJson.Num(op["hp"]);
}
break;
}
case "settle_boss":
Results.Add(new List { "settle_boss", -1L, "", true, State.SettleBoss() });
break;
case "expedition":
{
int slot = MiniJson.Int(op["slot"]);
State.Players[slot].Draft.Gold += 6000;
List ids = State.BaseIds(State.Players[slot].Bund);
foreach (object typeIndex in MiniJson.Arr(op["types"]))
{
Record(slot, Command.Buy(ids[MiniJson.Int(typeIndex)]));
RosterEntry entry = State.Players[slot].Draft.Roster[State.Players[slot].Draft.Roster.Count - 1];
Record(slot, Command.Upgrade(entry.Uid));
Record(slot, Command.Assign(entry.Uid, "grotto"));
}
if (op.ContainsKey("hero"))
{
Record(slot, Command.HeroBuy(MiniJson.Str(op["hero"])));
Record(slot, Command.Assign(State.Players[slot].Draft.Hero.Uid, "grotto"));
}
break;
}
case "play":
{
bool bots = !op.ContainsKey("bots") || (bool)op["bots"];
string profile = op.ContainsKey("profile") ? MiniJson.Str(op["profile"]) : "";
for (int i = 0; i < MiniJson.Int(op["rounds"]); i++)
{
if (State.Phase == Phase.Over)
break;
if (bots)
{
for (int slot = 0; slot < 6; slot++)
LuneBot.Plan(State, slot, profile);
}
int fought = State.RoundNumber;
State.BeginCombat();
State.FinishCombat();
var label = new OrderedMap();
label["label"] = "round-" + fought;
Snapshot(label);
}
break;
}
case "snapshot":
Snapshot(op);
break;
default:
throw new InvalidOperationException("unknown op " + MiniJson.Str(op["op"]));
}
}
void Record(int slot, Command command)
{
CommandResult result = State.Submit(slot, command);
Results.Add(new List { "cmd", (long)slot, command.Type, result.Ok, result.Message });
}
Command Resolve(OrderedMap source)
{
var command = new Command { Type = MiniJson.Str(source["type"]) };
foreach (KeyValuePair pair in source)
{
switch (pair.Key)
{
case "type_id": command.TypeId = MiniJson.Str(pair.Value); break;
case "lane": command.Lane = MiniJson.Str(pair.Value); break;
case "uid": command.Uid = ResolveUid(MiniJson.Str(pair.Value)); break;
case "hero_id": command.HeroId = MiniJson.Str(pair.Value); break;
case "item_id": command.ItemId = MiniJson.Str(pair.Value); break;
case "champion_id": command.ChampionId = MiniJson.Str(pair.Value); break;
case "tier": command.Tier = MiniJson.Int(pair.Value); break;
case "choice": command.Choice = MiniJson.Int(pair.Value); break;
}
}
return command;
}
string ResolveUid(string uid)
{
if (!uid.StartsWith("$", StringComparison.Ordinal))
return uid;
string[] parts = uid.Substring(1).Split(':');
Player owner = State.Players[int.Parse(parts[1])];
switch (parts[0])
{
case "last": return owner.Draft.Roster[owner.Draft.Roster.Count - 1].Uid;
case "roster": return owner.Draft.Roster[int.Parse(parts[2])].Uid;
case "hero": return owner.Draft.Hero.Uid;
case "booking": return owner.Draft.NewBookings[int.Parse(parts[2])].Uid;
}
return uid;
}
// ---------------------------------------------------------------- snapshot (same shape as the Godot tool)
static string Bool(bool value) { return value ? "true" : "false"; }
void Snapshot(OrderedMap op)
{
var players = new List();
foreach (Player player in State.Players)
{
var entry = new OrderedMap();
entry["gold"] = (long)player.Gold;
entry["draft_gold"] = (long)player.Draft.Gold;
entry["ready"] = player.Ready;
entry["earned"] = (long)player.Earned;
entry["spent"] = (long)player.Spent;
entry["next_seq"] = (long)player.NextSeq;
entry["roster"] = Roster(player.Roster, false);
entry["hero"] = Hero(player.Hero);
entry["bookings"] = player.Bookings.Select(b => (object)(b.Uid + "|" + b.ChampionId + "|" + b.Lane + "|" + b.Cost + "|" + b.DeployRound)).ToList();
entry["new_bookings"] = player.Draft.NewBookings.Select(b => (object)(b.Uid + "|" + b.ChampionId + "|" + b.Lane + "|" + b.Cost)).ToList();
entry["draft_roster"] = Roster(player.Draft.Roster, true);
entry["draft_hero"] = Hero(player.Draft.Hero);
entry["boss_lane"] = player.Draft.BossLane;
entry["undo"] = (long)player.Undo.Count;
players.Add(entry);
}
var snap = new OrderedMap();
snap["label"] = MiniJson.Str(op["label"]);
snap["phase"] = (long)(int)State.Phase;
snap["round"] = (long)State.RoundNumber;
snap["winner"] = (long)State.Winner;
snap["end_note"] = State.EndNote;
snap["players"] = players;
snap["buildings"] = State.Map.Buildings.Select(d => (object)State.BuildingHp[d.Id]).ToList();
var boss = new OrderedMap();
boss["next"] = (long)State.Boss.NextAvailableRound;
boss["settled"] = (long)State.Boss.SettledRound;
BossClaim claim = State.Boss.Claim;
boss["claim"] = claim == null ? "" : claim.Team + "|" + claim.WonRound + "|" + claim.DeployRound + "|" + claim.ChooserSlot + "|" + claim.Lane;
snap["boss"] = boss;
snap["boss_log"] = State.BossLog.Select(e => (object)(e.Round + "|" + e.Event + "|" + e.Team + "|" + e.Lane + "|" + e.Chooser)).ToList();
snap["announcements"] = State.Announcements().Select(a => (object)(a.Kind + "|" + a.Team + "|" + a.Slot + "|" + a.Lane + "|" + a.ChampionId + "|" + a.Bounty)).ToList();
snap["settlement"] = SettlementSnapshot();
snap["combat"] = CombatSnapshot();
if (op.ContainsKey("units") && (bool)op["units"] && State.Combat != null)
snap["units"] = State.Combat.Units.Select(u => (object)UnitSnapshot(u)).ToList();
if (op.ContainsKey("events") && (bool)op["events"] && State.Combat != null)
{
snap["events"] = State.Combat.Events.Select(e => (object)new List { (long)e.Serial, (long)e.Step, e.Time, (long)e.Team, e.Lane, e.Zone, e.Kind,
(long)e.UnitId, e.DefId, (long)e.Bounty, e.BuildingId, (long)e.Xp, e.XpHeroes.Select(h => (object)h).ToList() }).ToList();
}
if (op.ContainsKey("wave") && (bool)op["wave"])
snap["wave"] = LuneWaves.Build(State.Balance, State.RoundNumber).Select(m => (object)new List { m.Id, m.MaxHp, m.Damage, (long)m.Bounty }).ToList();
Snapshots.Add(snap);
}
static List Roster(List roster, bool withFresh)
{
return roster.Select(e => (object)(e.Uid + "|" + e.TypeId + "|" + e.Lane + "|" + e.Invested + (withFresh ? "|" + Bool(e.Fresh) : ""))).ToList();
}
static string Hero(HeroState hero)
{
if (hero == null)
return "";
return hero.Uid + "|" + hero.HeroId + "|" + hero.Xp + "|" + hero.Lane + "|" + string.Join("+", hero.Items) + "|" + string.Join("+", hero.Talents) + "|"
+ hero.Invested + "|" + Bool(hero.Fresh) + "|" + string.Join("+", hero.FreshItems);
}
object SettlementSnapshot()
{
Settlement s = State.LastSettlement;
if (s == null)
return null;
var result = new OrderedMap();
result["round"] = (long)s.Round;
result["gains"] = s.Gains.Select(g => (object)(long)g).ToList();
result["income"] = (long)s.Income;
result["lost"] = s.BuildingsLost.Select(b => (object)b).ToList();
result["end_reason"] = s.EndReason;
result["combat_time"] = s.CombatTime;
result["boss"] = s.Boss;
var laneKills = new OrderedMap();
foreach (KeyValuePair pair in s.LaneKills)
laneKills[pair.Key] = (long)pair.Value;
result["lane_kills"] = laneKills;
var xp = new OrderedMap();
foreach (KeyValuePair pair in s.Xp)
xp[pair.Key] = (long)pair.Value;
result["xp"] = xp;
return result;
}
object CombatSnapshot()
{
LuneCombat c = State.Combat;
if (c == null)
return null;
var result = new OrderedMap();
result["round"] = (long)c.RoundNumber;
result["steps"] = (long)c.Steps;
result["elapsed"] = c.Elapsed;
result["finished"] = c.Finished;
result["end_reason"] = c.EndReason;
result["destroyed"] = c.DestroyedCores.Select(t => (object)(long)t).ToList();
result["grotto"] = c.GrottoOutcome + "|" + c.GrottoClaimTeam + "|" + c.GrottoActiveTeam + "|" + Bool(c.GrottoResolved);
result["grotto_boss"] = c.GrottoBoss == null ? "" : Bool(c.GrottoBoss.Alive) + "|" + Bool(c.GrottoBoss.Dormant) + "|" + c.GrottoBoss.Id;
result["grotto_boss_hp"] = c.GrottoBoss != null ? c.GrottoBoss.Hp : 0.0;
result["events"] = (long)c.Events.Count;
result["mobile"] = new List { (long)c.MobileCount(0), (long)c.MobileCount(1), (long)c.MobileCount(2) };
return result;
}
static OrderedMap UnitSnapshot(LuneUnit unit)
{
var result = new OrderedMap();
result["id"] = (long)unit.Id;
result["kind"] = unit.Kind;
result["team"] = (long)unit.Team;
result["lane"] = unit.Lane;
result["zone"] = unit.Zone;
result["slot"] = (long)unit.OwnerSlot;
result["uid"] = unit.PersistentUid;
result["def"] = unit.Def.Id;
result["alive"] = unit.Alive;
result["hp"] = unit.Hp;
result["max_hp"] = unit.MaxHp;
result["shield"] = unit.Shield;
result["x"] = (double)unit.Position.X;
result["y"] = (double)unit.Position.Y;
result["facing"] = unit.Facing;
result["progress"] = unit.Progress;
result["target"] = (long)(unit.Target != null ? unit.Target.Id : -1);
result["hits"] = (long)unit.HitCount;
result["reached"] = unit.ReachedBase;
result["dormant"] = unit.Dormant;
result["effects"] = unit.Effects.Select(p => (object)new List { p.Key, p.Value.Kind, p.Value.Amount, p.Value.Remaining }).ToList();
result["dots"] = unit.Dots.Select(p => (object)new List { p.Key, p.Value.Damage, p.Value.Remaining, p.Value.NextTick, (long)p.Value.AttackType }).ToList();
return result;
}
}
}