saumwacht-godot/scripts/luneraria/ui/lune_menu.gd
Martin Stickelbroeck 214ed5a1cc M4: autoritative Host/Join-Sitzung, Protokolltests, Loopback und Demos
LuneSession hält den autoritativen Matchzustand; Clients senden nur
Absichten. Fremde Sitze, veraltete Befehle und Doppelkäufe sind geprüft,
Disconnect führt zur Bot-Übernahme statt zum Kernverlust. Exporte und
entpackbare Demos 0.16.0-m4 für Linux und Windows.
2026-09-28 00:57:02 +02:00

119 lines
5 KiB
GDScript

extends MainMenu
## The Phase 20A menu shell retitled for Champions of Luneraria. Match options here are
## the new mode's options (own Bund, lane, seed); they never touch the legacy GameState.
signal host_requested(port: int, display_name: String)
signal join_requested(address: String, port: int, display_name: String)
var bund: String = "wurzelbund"
var lane: String = "mid"
var lane_choice: ChoiceGroup
var _net_name: LineEdit
var _net_address: LineEdit
var _net_port: LineEdit
func _ready() -> void:
super._ready()
title_label.text = "CHAMPIONS"
subtitle_label.text = "OF LUNERARIA"
subtitle_label.theme_type_variation = "TitleLabel"
subtitle_label.add_theme_font_size_override("font_size", 54)
title_label.add_theme_font_size_override("font_size", 64)
var tagline := Label.new()
tagline.text = "DREI LANES · 3 GEGEN 3 · AUTOBATTLER"
tagline.theme_type_variation = "SubtitleLabel"
brand_names.add_child(tagline)
lore_label.text = "Führe deine Armee über drei Lanes. Die Partie endet erst, wenn ein Ancient fällt."
emblem_rect.texture = load("res://assets/ui/emblems/luneraria.svg")
version_label.text = "Prototyp %s · 1 Mensch + 5 Bots oder 6 über Direktverbindung" % str(ProjectSettings.get_setting("application/config/version", ""))
_title_caption.text = "Glutpakt und Wurzelbund an den Ufern der Grünen Kluft"
_retarget_setup()
func _retarget_setup() -> void:
# Replace the Saumwacht-only options (match length, rival, AI level, timed build).
faction_choice.item_selected.disconnect(faction_choice.item_selected.get_connections()[0].callable)
faction_choice.item_selected.connect(func(index: int) -> void:
bund = DataCatalog.FACTIONS[index]
sync())
var body: VBoxContainer = mode_choice.get_parent()
var at: int = mode_choice.get_index() - 1
for node: Node in [mode_choice, length_summary, _advanced_button.get_parent(), _advanced]:
node.visible = false
body.get_child(at).visible = false
var heading := Label.new()
heading.text = "DEINE LANE"
heading.theme_type_variation = "SectionLabel"
body.add_child(heading)
body.move_child(heading, at)
lane_choice = ChoiceGroup.new(true)
lane_choice.add_option("Top", "oberer Korridor")
lane_choice.add_option("Mid", "direkter Weg")
lane_choice.add_option("Bottom", "unterer Korridor")
body.add_child(lane_choice)
body.move_child(lane_choice, at + 1)
lane_choice.item_selected.connect(func(index: int) -> void:
lane = LuneMap.LANES[index]
sync())
var rules := Label.new()
rules.theme_type_variation = "MutedLabel"
rules.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
rules.text = "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."
body.add_child(rules)
body.move_child(rules, at + 2)
var net_heading := Label.new()
net_heading.text = "NETZWERK · DIREKTVERBINDUNG"
net_heading.theme_type_variation = "SectionLabel"
body.add_child(net_heading)
_net_name = LineEdit.new()
_net_name.placeholder_text = "Anzeigename"
_net_name.text = "Spieler"
body.add_child(_net_name)
var net_row := HBoxContainer.new()
_net_address = LineEdit.new()
_net_address.placeholder_text = "Host-Adresse"
_net_address.text = "127.0.0.1"
_net_address.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_net_port = LineEdit.new()
_net_port.placeholder_text = "Port"
_net_port.text = "24571"
_net_port.custom_minimum_size.x = 90
net_row.add_child(_net_address)
net_row.add_child(_net_port)
body.add_child(net_row)
var buttons := HBoxContainer.new()
var host_btn := Button.new()
host_btn.text = "Hosten"
host_btn.tooltip_text = "Partie eröffnen. Freie Plätze sind Bots."
host_btn.pressed.connect(func() -> void: host_requested.emit(int(_net_port.text), _net_name.text))
var join_btn := Button.new()
join_btn.text = "Beitreten"
join_btn.tooltip_text = "Mit Host und Port verbinden."
join_btn.pressed.connect(func() -> void: join_requested.emit(_net_address.text.strip_edges(), int(_net_port.text), _net_name.text))
buttons.add_child(host_btn)
buttons.add_child(join_btn)
body.add_child(buttons)
func sync() -> void:
super.sync()
if lane_choice == null:
return
faction_choice.select(DataCatalog.FACTIONS.find(bund))
lane_choice.select(LuneMap.LANES.find(lane))
_style_heading.text = "SPIELWEISE · " + DataCatalog.FACTION_NAMES[bund].to_upper()
var lines: PackedStringArray = []
for fact: String in FactionChoice.PROFILES[bund].facts:
lines.append("• " + fact)
_style_facts.text = "\n".join(lines)
_timed_hint.text = "Planung wartet auf dich · Leertaste startet den Kampf"
var names: PackedStringArray = []
for entry: Array in Stage.SETUPS[bund].figures:
names.append(str(DataCatalog.get_unit(entry[0]).display_name))
_setup_caption.text = "IM BILD " + " · ".join(names)
if screen == Screen.SETUP and _active and stage.current_shot() != bund:
stage.set_shot_named(bund, reduced_motion)
func _setup_shot() -> String:
return bund
func _on_title_primary() -> void:
show_screen(Screen.SETUP)