saumwacht-godot/scripts/luneraria/lune_main.gd
Martin Stickelbroeck c15533831a M4: Spiel läuft vollständig über die autoritative Sitzung; M4 pausiert
Das echte Spiel umging die Sitzung bisher bei Bereitschaft, Startaufstellung
und Rückgängig, ließ Bots den Gast-Sitz planen und den Gast selbst simulieren.
Jetzt rechnet nur der Host; Gäste zeigen den Kampf aus Momentaufnahmen.
Dazu Protokollversion, Wiedereinstieg über den Anzeigenamen, verbindliches
Bereit, Entwurfs-Privatsphäre und Bot-Planung einmal pro Runde.

Geprüft: test_lune_net 43/43, net_rounds.py 12/12 (5 Runden, Wiedereinstieg,
gleiche Prüfsummen), Regeln 91/91, M2 73/73, verify_m1 38/38, verify_m2 14/14.
Offen und eingefroren: OpenGL-Modus, nativer Windows-11-Test, Demo-Neubau.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:00:08 +02:00

155 lines
5.8 KiB
GDScript

extends Node
## Entry scene of Champions of Luneraria: title menu, match, return, rematch.
## Offline is a local host with bot-filled seats. Host/Join uses LuneSession (ENet).
## Legacy QA switches open the archived Saumwacht scene unchanged.
const Game = preload("res://scripts/luneraria/lune_game.gd")
const Menu = preload("res://scripts/luneraria/ui/lune_menu.gd")
const Session = preload("res://scripts/luneraria/net/lune_session.gd")
const LEGACY_SWITCHES: Array[String] = ["--smoke-test", "--faction-test", "--expedition-test", "--ringborke", "--legacy"]
var menu: MainMenu
var game: Node3D
var settings: Control
var session: Node
var _layer: CanvasLayer
var _last_options: Dictionary = {}
func _ready() -> void:
for arg: String in OS.get_cmdline_user_args():
if arg in LEGACY_SWITCHES:
get_tree().change_scene_to_file.call_deferred("res://scenes/main.tscn")
return
DisplayServer.window_set_title("Champions of Luneraria")
get_tree().auto_accept_quit = false
get_tree().root.close_requested.connect(Soundscape.quit_game)
_layer = CanvasLayer.new()
_layer.layer = 10
add_child(_layer)
menu = Menu.new()
_layer.add_child(menu)
menu.start_requested.connect(func() -> void: start_match({"seed": int(Time.get_unix_time_from_system()) % 100000}))
menu.quit_requested.connect(func() -> void: Soundscape.quit_game())
menu.settings_requested.connect(toggle_settings)
if menu.has_signal("host_requested"):
menu.host_requested.connect(_host_match)
menu.join_requested.connect(_join_match)
settings = LuneSettings.new()
_layer.add_child(settings)
settings.visible = false
settings.closed.connect(toggle_settings)
menu.enter()
func start_match(options: Dictionary) -> void:
var own_team_slots: Array[int] = [0, 1, 2]
var human: int = own_team_slots[LuneMap.LANES.find(menu.lane)] if menu.lane in LuneMap.LANES else 1
var bunds: Array = []
var rng := RandomNumberGenerator.new()
rng.seed = int(options.get("seed", 1))
for slot: int in range(6):
bunds.append(menu.bund if slot == human else DataCatalog.FACTIONS[rng.randi() % DataCatalog.FACTIONS.size()])
_last_options = {"seed": int(options.get("seed", 1)), "human_slot": human, "bunds": bunds}
_launch(_last_options)
func _host_match(port: int, display_name: String) -> void:
_reset_session()
session = Session.new()
session.name = "LuneSession"
add_child(session)
var options := {"seed": int(Time.get_unix_time_from_system()) % 100000, "human_slot": 0, "bunds": []}
var rng := RandomNumberGenerator.new()
rng.seed = options.seed
for slot: int in range(6):
options.bunds.append(menu.bund if slot == 0 else DataCatalog.FACTIONS[rng.randi() % DataCatalog.FACTIONS.size()])
var err: int = session.host_enet(port, options, display_name)
if err == OK:
_last_options = options
_launch(_last_options)
else:
push_warning(session.last_error)
menu.notify_error(session.last_error)
_reset_session()
func _join_match(address: String, port: int, display_name: String) -> void:
_reset_session()
session = Session.new()
session.name = "LuneSession"
add_child(session)
session.session_message.connect(func(text: String, is_error: bool) -> void:
print(("FEHLER: " if is_error else "") + text))
session.host_lost.connect(func() -> void:
var reason: String = session.last_error if session != null else ""
return_to_menu()
menu.notify_error(reason))
if session.join_enet(address, port, display_name) != OK:
menu.notify_error(session.last_error)
_reset_session()
return
# Every setup from the host (first join, rejoin, rematch) relaunches the match view.
session.match_resetup.connect(func() -> void:
if session != null and session.match_state != null:
_last_options = {"seed": session.match_state.seed_value, "human_slot": session.local_slot, "bunds": []}
_launch(_last_options))
func _reset_session() -> void:
if session != null and is_instance_valid(session):
session.close()
session.queue_free()
session = null
func _launch(options: Dictionary) -> void:
# The rematch button lives inside the old match; detach it now, free it after the signal.
if is_instance_valid(game):
remove_child(game)
game.queue_free()
game = Game.new()
add_child(game)
game.input_blocked = func() -> bool: return menu.visible or settings.visible
if session != null and is_instance_valid(session):
game.session = session
game.start(options)
game.exit_requested.connect(return_to_menu)
game.rematch_requested.connect(_rematch)
game.hud.settings_requested.connect(toggle_settings)
menu.leave()
func _rematch() -> void:
_last_options.seed = int(_last_options.seed) + 1
if session != null and is_instance_valid(session) and session.is_networked():
if not session.is_authority():
game.hud.notify("Eine Revanche startet der Host.", true)
return
session.host_rematch(_last_options.duplicate(true))
_launch(_last_options)
func return_to_menu() -> void:
if is_instance_valid(game):
remove_child(game)
game.queue_free()
game = null
_reset_session()
menu._locked = false
menu.pending_screen = MainMenu.Screen.TITLE
menu.enter()
func toggle_settings() -> void:
settings.visible = not settings.visible
# Only an offline match pauses behind the settings; a network match keeps running.
var networked: bool = session != null and is_instance_valid(session) and session.is_networked()
if is_instance_valid(game) and not networked:
game.paused = settings.visible or game.paused
if settings.visible:
settings.sync()
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed and not event.echo:
if event.keycode == KEY_F1 or (event.keycode == KEY_ESCAPE and settings.visible):
toggle_settings()
get_viewport().set_input_as_handled()
elif event.keycode in [KEY_M, KEY_F11]:
if event.keycode == KEY_M:
Preferences.muted = not Preferences.muted
else:
Preferences.fullscreen = not Preferences.fullscreen
Preferences.commit()
get_viewport().set_input_as_handled()