champions-of-luneraria/Assets/Luneraria/Core/Simulation/MatchModel.cs
Martin Stickelbroeck fb5c92dd37 S3 Neutrallager und Fog of War mit Spähern (LoL, Dota 2)
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>
2026-10-11 16:30:57 +02:00

279 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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