Erweiterte Regeln: in jeder dritten Runde ein neutraler Wächter in jeder Lanemitte; wer beim Fall mehr Einheiten (dann mehr Leben) in 8 m hat, nimmt das Lager: Glutsegen (+10 % Schaden), Mondschild (Schild 10 %), Beute (50 Gold je Spieler). Fog of War (LuneFog, nur Darstellung): Gegner sichtbar in 9 m um eigene Einheiten, 10 m um Gebäude, 12 m um Späher; Späher (40 Gold, 3 Runden, Teamwirkung) zeigen in der Planung die gegnerische Armee ihrer Lane. HUD: Teampanel SPÄHER und LAGER, Lager-Sechseck, Nebel in Feld, Minimap, Namensschildern und Lane-Zählung. Kern 597/597 (s3 31), EditMode 33/33, PlayMode 18/18, Verify 718/0, Selbsttest 30/30, Golden 0, Fingerabdruck unverändert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
279 lines
12 KiB
C#
279 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;
|
||
/// <summary>S3: lanes the player places a ward on in this planning.</summary>
|
||
public List<string> WardLanes = new List<string>();
|
||
|
||
public Draft Clone()
|
||
{
|
||
var copy = new Draft { Gold = Gold, NextSeq = NextSeq, BossLane = BossLane, GlyphLane = GlyphLane, CoreInvest = CoreInvest, Hero = Hero != null ? Hero.Clone() : null,
|
||
WardLanes = new List<string>(WardLanes) };
|
||
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];
|
||
/// <summary>S3 (extended rules): combats left of the camp buffs, index team × 3 + lane (top Glutsegen, mid Mondschild).</summary>
|
||
public int[] CampBuff = new int[6];
|
||
/// <summary>S3 (extended rules): rounds left of each team's ward per lane (team × 3 + lane).</summary>
|
||
public int[] Wards = new int[6];
|
||
}
|
||
|
||
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" }; }
|
||
/// <summary>S3: place a ward on a lane (fog of war).</summary>
|
||
public static Command Ward(string lane) { return new Command { Type = "ward", Lane = lane }; }
|
||
}
|
||
}
|