champions-of-luneraria/Assets/Luneraria/Core/Simulation/MatchModel.cs
Martin Stickelbroeck 82066d17a6 S2 Glyphe, Item-Rezepte und Ancient-Stufen (Dota 2, LoL, Legion TD 2)
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>
2026-10-11 12:54:46 +02:00

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" }; }
}
}