champions-of-luneraria/Assets/Luneraria/UI/Game/LuneMainMenu.cs
Martin Stickelbroeck 8e4b52f4ab S1 Strategie-Regeln nach Legion TD 2, Dota 2 und League of Legends
Hinter MatchOptions.Extended (Standard im Spiel, Schalter im Startbildschirm):
Söldner-Einkommen (Angriffschampion +Preis/12 Einkommen dauerhaft), Kasernen
(gefallener innerer Turm: Gegnerwellen +25 % und ein Monster mehr), Erschöpfung
und Rückkauf (60 % Leben, Rückkauf 60 + 20 x Stufe), Mondsegen (Grottensieg
+4 % je Stapel, Mondseele bei 4). Bots wählen mit erweiterten Regeln auch
Sylvara und kaufen Helden zurück. Netz überträgt Regeln und neue Zustände.
HUD: Einkommen, Kasernen-Symbol, Rückkauf, Segen im Teamkopf.
Kern 531/531 (s1 31), EditMode 31/31, PlayMode 16/16, Verify 718/0,
Selbsttest 30/30, Golden 0 Abweichungen, Fingerabdruck unverändert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:18:47 +02:00

208 lines
8.6 KiB
C#
Raw Permalink 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;
using Luneraria.Core;
using UnityEngine;
using UnityEngine.UIElements;
namespace Luneraria.UI
{
/// <summary>
/// Start screen of Champions of Luneraria (port of the network part of lune_menu.gd):
/// offline (local host with bot seats), host a direct connection, or join one. The menu is
/// the start screen of the game; the HUD's "Menü" returns here. Peer 1 is the host; free
/// seats stay bots. The host is authoritative.
/// </summary>
public sealed class LuneMainMenu : VisualElement
{
public event Action<MatchOptions> StartOffline;
public event Action<int, string> HostRequested;
public event Action<string, int, string> JoinRequested;
public event Action QuitRequested;
public string Bund = "wurzelbund";
public string Lane = "mid";
readonly TextField _name;
readonly TextField _address;
readonly TextField _port;
readonly Label _status;
LuneButton _bundGlut;
LuneButton _bundWurzel;
LuneButton _laneTop;
LuneButton _laneMid;
LuneButton _laneBot;
/// <summary>S1: the rules toggle (extended or reference rules).</summary>
public LuneButton RulesToggle;
public LuneMainMenu()
{
style.position = Position.Absolute;
style.left = 0f;
style.top = 0f;
style.right = 0f;
style.bottom = 0f;
style.justifyContent = Justify.Center;
style.alignItems = Align.Center;
style.backgroundColor = new Color(.02f, .03f, .05f, .92f);
var panel = new LunePanel(LunePanels.Menu);
panel.style.minWidth = 560f;
// The four action buttons need about 650 px; at 640 they stuck out of the frame.
panel.style.maxWidth = 760f;
Add(panel);
VisualElement column = LuneUi.Box(true, 14f, panel);
column.style.alignItems = Align.Center;
LuneUi.Label("CHAMPIONS", "TitleLabel", column).style.fontSize = 56f;
LuneUi.Label("OF LUNERARIA", "TitleLabel", column).style.fontSize = 40f;
LuneUi.Label("DREI LANES · 3 GEGEN 3 · AUTOBATTLER", "EyebrowLabel", column);
LuneUi.Wrap(LuneUi.Label("Team Blau (Südwest) mit zwei Bot-Verbündeten gegen drei Bots in Team Rot. Kein Rundenlimit: Es gewinnt, wer den gegnerische Ancient zerstört. Für sechs Menschen hosten oder beitreten; freie Plätze bleiben Bots. Der Host ist autoritativ.", "MutedLabel", column));
LuneUi.Label("DEIN BUND", "SectionLabel", column);
VisualElement bundRow = LuneUi.Box(false, 10f, column);
_bundGlut = new LuneButton("Glutpakt", LuneButtons.HudToggle, bundRow);
_bundWurzel = new LuneButton("Wurzelbund", LuneButtons.HudToggle, bundRow);
_bundGlut.ToggleMode = true;
_bundWurzel.ToggleMode = true;
_bundGlut.Clicked += () => SetBund("glutpakt");
_bundWurzel.Clicked += () => SetBund("wurzelbund");
SetBund(Bund);
LuneUi.Label("DEINE LANE", "SectionLabel", column);
VisualElement laneRow = LuneUi.Box(false, 10f, column);
_laneTop = new LuneButton("Top", LuneButtons.HudToggle, laneRow);
_laneMid = new LuneButton("Mid", LuneButtons.HudToggle, laneRow);
_laneBot = new LuneButton("Bottom", LuneButtons.HudToggle, laneRow);
_laneTop.ToggleMode = true;
_laneMid.ToggleMode = true;
_laneBot.ToggleMode = true;
_laneTop.Clicked += () => SetLane("top");
_laneMid.Clicked += () => SetLane("mid");
_laneBot.Clicked += () => SetLane("bot");
SetLane("mid");
LuneUi.Label("REGELN", "SectionLabel", column);
RulesToggle = new LuneButton("", LuneButtons.HudToggle, column);
RulesToggle.ToggleMode = true;
RulesToggle.Clicked += () => SetRules(!LunePreferences.ExtendedRules);
LuneTooltip.Attach(RulesToggle, () => new LuneTip
{
Title = "Erweiterte Regeln (S1)", Glyph = "star", GlyphColor = LuneUiTheme.Hero,
Text = "Söldner-Einkommen wie in Legion TD 2: Angriffschampions erhöhen dauerhaft dein Einkommen.\nKasernen wie in Dota 2: Fällt ein innerer Turm, kommen die gegnerischen Wellen dort verstärkt.\nRückkauf: Gefallene Helden starten erschöpft, außer du kaufst sie zurück.\nMondsegen wie die Drachenseelen in League of Legends: Jeder Bosssieg stärkt dein Team dauerhaft.\nBots wählen auch Sylvara.",
Note = "Aus: die Referenzregeln des Ports.",
});
SetRules(LunePreferences.ExtendedRules);
LuneUi.Label("NETZWERK · DIREKTVERBINDUNG", "SectionLabel", column);
_name = Field(column, "Anzeigename", "Spieler");
VisualElement netRow = LuneUi.Box(false, 10f, column);
_address = Field(netRow, "Host-Adresse", "127.0.0.1");
_address.style.flexGrow = 1f;
_port = Field(netRow, "Port", LuneNet.DefaultPort.ToString());
_port.style.width = 100f;
VisualElement actions = LuneUi.Box(false, 12f, column);
new LuneButton("ALLEIN SPIELEN", LuneButtons.Primary, actions).Clicked += StartAlone;
new LuneButton("HOSTEN", LuneButtons.Secondary, actions).Clicked += StartHost;
new LuneButton("BEITRETEN", LuneButtons.Secondary, actions).Clicked += StartJoin;
new LuneButton("BEENDEN", LuneButtons.Secondary, actions).Clicked += () => QuitRequested?.Invoke();
_status = LuneUi.Label("", "MutedLabel", column);
_status.style.unityTextAlign = TextAnchor.MiddleCenter;
}
static TextField Field(VisualElement parent, string label, string value)
{
VisualElement box = LuneUi.Box(true, 4f, parent);
box.style.flexGrow = 1f;
LuneUi.Label(label, "EyebrowLabel", box);
var field = new TextField { value = value };
field.style.flexGrow = 1f;
field.style.minHeight = 32f;
field.style.fontSize = 16f;
box.Add(field);
return field;
}
void SetBund(string bund)
{
Bund = bund;
_bundGlut.On = bund == "glutpakt";
_bundWurzel.On = bund == "wurzelbund";
}
void SetRules(bool extended)
{
LunePreferences.ExtendedRules = extended;
RulesToggle.On = extended;
RulesToggle.Text = extended ? "ERWEITERT · Legion TD 2, Dota 2, LoL" : "REFERENZ · Regeln des Ports";
}
void SetLane(string lane)
{
Lane = lane;
_laneTop.On = lane == "top";
_laneMid.On = lane == "mid";
_laneBot.On = lane == "bot";
}
/// <summary>Own seat on team Blau: slots 0–2 map to top, mid, bot.</summary>
public MatchOptions OfflineOptions()
{
int seat = Array.IndexOf(LuneMap.Lanes, Lane);
if (seat < 0)
seat = 1;
var options = new MatchOptions { Seed = Environment.TickCount & 0xffff, HumanSlot = seat, Extended = LunePreferences.ExtendedRules };
for (int slot = 0; slot < 6; slot++)
options.Bunds.Add(slot == seat ? Bund : DataCatalog.Factions[(slot + options.Seed) % DataCatalog.Factions.Length]);
return options;
}
void StartAlone()
{
StartOffline?.Invoke(OfflineOptions());
}
void StartHost()
{
int port = ParsePort();
HostRequested?.Invoke(port, CleanName());
}
void StartJoin()
{
int port = ParsePort();
JoinRequested?.Invoke(_address.value.Trim(), port, CleanName());
}
int ParsePort()
{
int port;
return int.TryParse(_port.value.Trim(), out port) && port > 0 ? port : LuneNet.DefaultPort;
}
string CleanName()
{
string name = (_name.value ?? "").Trim();
if (name.Length == 0)
name = "Spieler";
return name.Length > 24 ? name.Substring(0, 24) : name;
}
public void Notify(string message, bool error = false)
{
_status.text = message ?? "";
_status.style.color = error ? LuneUiTheme.Hex("ff8a70") : LuneUiTheme.Muted;
}
public void Enter()
{
LuneUi.Show(this, true);
Notify("");
}
public void Leave()
{
LuneUi.Show(this, false);
}
}
}