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>
270 lines
12 KiB
C#
270 lines
12 KiB
C#
using System.Collections.Generic;
|
|
|
|
namespace Luneraria.Core
|
|
{
|
|
public enum Phase { Planning, Combat, Over }
|
|
|
|
/// <summary>One persistent troop of a player (committed roster or planning draft).</summary>
|
|
public sealed class RosterEntry
|
|
{
|
|
/// <summary>"p{slot}-{seq}": unique per match; ownership is the slot in the uid.</summary>
|
|
public string Uid = "";
|
|
public int Seq;
|
|
public string TypeId = "";
|
|
/// <summary>top, mid, bot or grotto (one round).</summary>
|
|
public string Lane = "";
|
|
public int Invested;
|
|
/// <summary>Bought in the current planning (full refund on sale).</summary>
|
|
public bool Fresh;
|
|
|
|
public RosterEntry Clone() { return (RosterEntry)MemberwiseClone(); }
|
|
}
|
|
|
|
/// <summary>The single hero of a player.</summary>
|
|
public sealed class HeroState
|
|
{
|
|
public string Uid = "";
|
|
public int Seq;
|
|
public string HeroId = "";
|
|
public string Lane = "";
|
|
public int Xp;
|
|
public List<string> Items = new List<string>();
|
|
public List<string> Talents = new List<string>();
|
|
public int Invested;
|
|
public bool Fresh;
|
|
/// <summary>Items bought in the current planning (full refund on sale).</summary>
|
|
public List<string> FreshItems = new List<string>();
|
|
/// <summary>Trigger rule of the ultimate (H1), "" = the hero's default rule.</summary>
|
|
public string Ultimate = "";
|
|
/// <summary>H1: cosmetic skin ("" = standard), chosen freely in planning, visible to everyone.</summary>
|
|
public string Skin = "";
|
|
/// <summary>S1 (extended rules): fell in the last combat; starts the next one weakened unless bought back.</summary>
|
|
public bool Exhausted;
|
|
|
|
public HeroState Clone()
|
|
{
|
|
var copy = (HeroState)MemberwiseClone();
|
|
copy.Items = new List<string>(Items);
|
|
copy.Talents = new List<string>(Talents);
|
|
copy.FreshItems = new List<string>(FreshItems);
|
|
return copy;
|
|
}
|
|
}
|
|
|
|
/// <summary>An attack champion booking: bought in round n, fights once in round n + 1.</summary>
|
|
public sealed class Booking
|
|
{
|
|
public string Uid = "";
|
|
public int Seq;
|
|
public string ChampionId = "";
|
|
public string Lane = "";
|
|
public int Cost;
|
|
/// <summary>0 while uncommitted (draft); the round it fights in once committed.</summary>
|
|
public int DeployRound;
|
|
|
|
public Booking Clone() { return (Booking)MemberwiseClone(); }
|
|
}
|
|
|
|
/// <summary>Private planning of one player; validated and committed atomically at combat start.</summary>
|
|
public sealed class Draft
|
|
{
|
|
public int Gold;
|
|
public List<RosterEntry> Roster = new List<RosterEntry>();
|
|
public int NextSeq;
|
|
public HeroState Hero;
|
|
public List<Booking> NewBookings = new List<Booking>();
|
|
/// <summary>Lane chosen by the boss chooser in this planning, "" if unchanged.</summary>
|
|
public string BossLane = "";
|
|
/// <summary>S2: lane the player holds the team glyph ready for, "" = none.</summary>
|
|
public string GlyphLane = "";
|
|
/// <summary>S2: Ancient levels this player buys in this planning.</summary>
|
|
public int CoreInvest;
|
|
|
|
public Draft Clone()
|
|
{
|
|
var copy = new Draft { Gold = Gold, NextSeq = NextSeq, BossLane = BossLane, GlyphLane = GlyphLane, CoreInvest = CoreInvest, Hero = Hero != null ? Hero.Clone() : null };
|
|
foreach (RosterEntry entry in Roster)
|
|
copy.Roster.Add(entry.Clone());
|
|
foreach (Booking booking in NewBookings)
|
|
copy.NewBookings.Add(booking.Clone());
|
|
return copy;
|
|
}
|
|
}
|
|
|
|
/// <summary>One of six seats. Team = slot / 3, lane = slot % 3 (top, mid, bot).</summary>
|
|
public sealed class Player
|
|
{
|
|
public int Slot;
|
|
public int Team;
|
|
public string Lane = "";
|
|
public bool IsHuman;
|
|
public string Name = "";
|
|
public string Bund = "";
|
|
public int Gold;
|
|
public List<RosterEntry> Roster = new List<RosterEntry>();
|
|
public HeroState Hero;
|
|
public List<Booking> Bookings = new List<Booking>();
|
|
public bool Ready;
|
|
public int Earned;
|
|
public int Spent;
|
|
public int NextSeq;
|
|
public Draft Draft;
|
|
public List<Draft> Undo = new List<Draft>();
|
|
/// <summary>S1 (extended rules): permanent income from booked attack champions (Legion TD 2).</summary>
|
|
public int IncomeBonus;
|
|
}
|
|
|
|
public sealed class BossClaim
|
|
{
|
|
public int Team;
|
|
public int WonRound;
|
|
public int DeployRound;
|
|
public int ChooserSlot;
|
|
public string Lane = "";
|
|
|
|
public BossClaim Clone() { return (BossClaim)MemberwiseClone(); }
|
|
}
|
|
|
|
/// <summary>Public boss state: the grotto opens from NextAvailableRound; Claim is null or the conquest.</summary>
|
|
public sealed class BossState
|
|
{
|
|
public int NextAvailableRound = 1;
|
|
public BossClaim Claim;
|
|
/// <summary>Round whose settlement was already applied (idempotence), 0 = none.</summary>
|
|
public int SettledRound;
|
|
/// <summary>S1 (extended rules): Mondsegen stacks per team from grotto victories (League of Legends dragon souls).</summary>
|
|
public int[] Blessing = new int[2];
|
|
/// <summary>S2 (extended rules): first round each team's glyph can be held again (Dota 2).</summary>
|
|
public int[] GlyphReady = { 1, 1 };
|
|
/// <summary>S2 (extended rules): Ancient level per team, 0..3 (Legion TD 2 king upgrades).</summary>
|
|
public int[] CoreLevel = new int[2];
|
|
}
|
|
|
|
public sealed class BossLogEntry
|
|
{
|
|
public int Round;
|
|
/// <summary>deployed, won or failed.</summary>
|
|
public string Event = "";
|
|
public int Team = -1;
|
|
public string Lane = "";
|
|
public int Chooser = -1;
|
|
}
|
|
|
|
/// <summary>Public announcement of the current round: a champion or the deployed boss.</summary>
|
|
public sealed class Announcement
|
|
{
|
|
/// <summary>champion or boss.</summary>
|
|
public string Kind = "";
|
|
public int Team;
|
|
public int Slot;
|
|
public string Lane = "";
|
|
public string ChampionId = "";
|
|
public int Bounty;
|
|
}
|
|
|
|
public sealed class Settlement
|
|
{
|
|
public int Round;
|
|
public int[] Gains = new int[6];
|
|
public int Income;
|
|
public List<string> BuildingsLost = new List<string>();
|
|
public string EndReason = "";
|
|
public double CombatTime;
|
|
public OrderedMap<string, int> LaneKills = new OrderedMap<string, int>();
|
|
public OrderedMap<string, int> Xp = new OrderedMap<string, int>();
|
|
/// <summary>none, won, failed, deployed or repeat.</summary>
|
|
public string Boss = "none";
|
|
}
|
|
|
|
public sealed class HistoryEntry
|
|
{
|
|
public int Round;
|
|
public string EndReason = "";
|
|
public double CombatTime;
|
|
public int[] Buildings = new int[2];
|
|
public int[] Gold = new int[6];
|
|
public List<string> Lost = new List<string>();
|
|
public string Boss = "";
|
|
public int[] HeroLevels = new int[6];
|
|
}
|
|
|
|
public sealed class MatchOptions
|
|
{
|
|
public int Seed = 1;
|
|
/// <summary>-1 for six bots.</summary>
|
|
public int HumanSlot = 1;
|
|
/// <summary>Six Bünde; missing entries follow the seed.</summary>
|
|
public List<string> Bunds = new List<string>();
|
|
/// <summary>Optional fixed bot profile per slot (balance runs).</summary>
|
|
public List<string> Profiles = new List<string>();
|
|
/// <summary>
|
|
/// S1: extended rules after Legion TD 2, Dota 2 and League of Legends (champion income,
|
|
/// barracks, exhaustion and buyback, Mondsegen) and bots that may pick the heroes after
|
|
/// the reference. False = the reference rules of the port (golden replays, fingerprints).
|
|
/// </summary>
|
|
public bool Extended;
|
|
}
|
|
|
|
/// <summary>Bot-private notes per slot, derived only from its own actions and public outcomes.</summary>
|
|
public sealed class BotMemory
|
|
{
|
|
public bool HasChampionRound;
|
|
public int ChampionRound;
|
|
public bool HasBossFields;
|
|
public double Bar;
|
|
public int Sent;
|
|
}
|
|
|
|
public struct CommandResult
|
|
{
|
|
public bool Ok;
|
|
public string Message;
|
|
|
|
public CommandResult(bool ok, string message)
|
|
{
|
|
Ok = ok;
|
|
Message = message;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A player's intent during planning. Clients only ever send these; the authority applies
|
|
/// them through LuneMatch.Submit. Null fields mean "not given" (e.g. buy without lane uses
|
|
/// the player's own lane).
|
|
/// </summary>
|
|
public sealed class Command
|
|
{
|
|
public string Type = "";
|
|
public string TypeId;
|
|
public string Lane;
|
|
public string Uid;
|
|
public string HeroId;
|
|
public string ItemId;
|
|
public string ChampionId;
|
|
public int? Tier;
|
|
public int? Choice;
|
|
|
|
public static Command Buy(string typeId, string lane = null) { return new Command { Type = "buy", TypeId = typeId, Lane = lane }; }
|
|
public static Command Upgrade(string uid) { return new Command { Type = "upgrade", Uid = uid }; }
|
|
public static Command Sell(string uid) { return new Command { Type = "sell", Uid = uid }; }
|
|
public static Command Assign(string uid, string lane) { return new Command { Type = "assign", Uid = uid, Lane = lane }; }
|
|
public static Command HeroBuy(string heroId) { return new Command { Type = "hero_buy", HeroId = heroId }; }
|
|
public static Command HeroCancel() { return new Command { Type = "hero_cancel" }; }
|
|
public static Command ItemBuy(string itemId, string uid = null) { return new Command { Type = "item_buy", ItemId = itemId, Uid = uid }; }
|
|
public static Command ItemSell(string itemId) { return new Command { Type = "item_sell", ItemId = itemId }; }
|
|
public static Command Talent(int tier, int choice) { return new Command { Type = "talent", Tier = tier, Choice = choice }; }
|
|
public static Command ChampionBook(string championId, string lane = null) { return new Command { Type = "champion_book", ChampionId = championId, Lane = lane }; }
|
|
public static Command ChampionCancel(string uid) { return new Command { Type = "champion_cancel", Uid = uid }; }
|
|
public static Command BossLaneChoice(string lane) { return new Command { Type = "boss_lane", Lane = lane }; }
|
|
/// <summary>H1: trigger rule of the hero's ultimate; the rule id travels in TypeId.</summary>
|
|
public static Command UltimateRule(string rule) { return new Command { Type = "ultimate_rule", TypeId = rule }; }
|
|
/// <summary>H1: cosmetic skin of the hero ("" = standard); the skin id travels in TypeId.</summary>
|
|
public static Command HeroSkin(string skin) { return new Command { Type = "hero_skin", TypeId = skin }; }
|
|
/// <summary>S1: buys an exhausted hero back to full strength for the next combat.</summary>
|
|
public static Command Buyback() { return new Command { Type = "buyback" }; }
|
|
/// <summary>S2: hold the team glyph for a lane in the coming combat ("" withdraws it).</summary>
|
|
public static Command Glyph(string lane) { return new Command { Type = "glyph", Lane = lane }; }
|
|
/// <summary>S2: buy the next Ancient level for the team.</summary>
|
|
public static Command CoreUpgrade() { return new Command { Type = "core_upgrade" }; }
|
|
}
|
|
}
|