diff --git a/README.md b/README.md index 992152d..1c2e540 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Der aktuelle Stand ist bewusst ein technischer Prototyp. Es ist noch keine ferti - Ein lokaler Loopback-Test simuliert Paketverkehr ohne zweiten Steam-Client. - Brotatos Run-Szene wird per Script-Extension beobachtet. - Run-Start, Spieler-Spawn und einfache Player-Snapshots werden geloggt bzw. fuer P2P-Versand vorbereitet. +- Das Brogether-Modal ist standardmaessig versteckt und kann ueber das Hauptmenue oder `F5` geoeffnet werden. ## Was noch nicht fertig ist @@ -25,16 +26,17 @@ Der aktuelle Stand ist bewusst ein technischer Prototyp. Es ist noch keine ferti ```text src/mods-unpacked/Brogether-Brogether/ - manifest.json Mod-Metadaten fuer Godot Mod Loader - mod_main.gd Steam-Lobby, P2P-Pakete, Debug-Overlay, Logging - extensions/main_ext.gd Hook in Brotatos Run-Szene + manifest.json Mod-Metadaten fuer Godot Mod Loader + mod_main.gd Steam-Lobby, P2P-Pakete, Brogether-Modal, Logging + extensions/main_ext.gd Hook in Brotatos Run-Szene + extensions/main_menu_ext.gd Brogether-Menuepunkt im Hauptmenue scripts/ - package.ps1 Baut das Mod-ZIP - install-local.ps1 Installiert das ZIP fuer lokale Brotato-Tests + package.ps1 Baut das Mod-ZIP + install-local.ps1 Installiert das ZIP fuer lokale Brotato-Tests docs/ - setup.md Setup- und Testnotizen + setup.md Setup- und Testnotizen brotato-multiplayer-modding-research.md ``` @@ -50,10 +52,12 @@ Brotato laedt in der Steam-Version nur abonnierte Workshop-Ordner. Das Install-S Danach Brotato komplett neu starten. +Brogether ist im Spiel standardmaessig unsichtbar. Im Hauptmenue gibt es einen neuen `BROGETHER`-Button; alternativ oeffnet `F5` das Brogether-Modal. Das Modal pausiert das Spiel wie Brotatos eigenes F8-Feedback-Fenster und kann mit `Escape`, `F5` oder dem Close-Button wieder geschlossen werden. + ## Debug-Tasten ```text -F5 Debug-Panel ein-/ausblenden +F5 Brogether-Modal ein-/ausblenden F6 Steam-Lobby erstellen F7 Lobby suchen / joinen F9 Lobby verlassen @@ -61,7 +65,7 @@ F10 Ping senden F11 Lokalen Loopback-Test starten ``` -Zusätzlich zeigt Brogether ein kleines Debug-Panel oben rechts mit Buttons fuer Host, Join, Leave, Ping und Loopback. Das Panel ist fuer die naechsten Zwei-Client-Tests gedacht und ersetzt die meisten Hotkey-Aktionen. +Das Brogether-Modal enthaelt Buttons fuer Host, Join, Leave, Ping und Loopback. Es ist fuer die naechsten Zwei-Client-Tests gedacht und ersetzt die meisten Hotkey-Aktionen. Brogether schreibt ein eigenes Log nach: @@ -74,9 +78,10 @@ C:\Users\tommi\AppData\Roaming\Brotato\brogether.log Sobald ein zweiter Steam-Client verfuegbar ist: 1. Beide Clients mit derselben Brogether-Version starten. -2. Host drueckt `F6`. -3. Client drueckt `F7`. -4. Mit `F10` Ping/Pong testen. -5. Einen Run starten und pruefen, ob `run_started`, `players_spawned` und Snapshot-Pakete auf beiden Seiten im Log erscheinen. +2. Host oeffnet `BROGETHER` im Hauptmenue oder drueckt `F5`. +3. Host klickt `Host` oder drueckt `F6`. +4. Client klickt `Join` oder drueckt `F7`. +5. Mit `Ping` oder `F10` Ping/Pong testen. +6. Einen Run starten und pruefen, ob `run_started`, `players_spawned` und Snapshot-Pakete auf beiden Seiten im Log erscheinen. Wenn das stabil laeuft, ist der naechste Entwicklungsschritt die echte Remote-Spielersteuerung auf Basis der Snapshot- und Input-Pakete. diff --git a/docs/brotato-multiplayer-modding-research.md b/docs/brotato-multiplayer-modding-research.md index 2df947d..5d79c21 100644 --- a/docs/brotato-multiplayer-modding-research.md +++ b/docs/brotato-multiplayer-modding-research.md @@ -58,7 +58,7 @@ Warum nicht sofort voll deterministisch/lockstep: Brotato hat viele zufalls- und ## MVP-Vorschlag -1. Mod-Skeleton laedt in Brotato und zeigt einen Multiplayer-Button oder Debug-Hotkeys. +1. Mod-Skeleton laedt in Brotato und zeigt einen versteckten Multiplayer-Dialog ueber Hauptmenue-Button oder Hotkey. 2. Steam-Lobby erstellen/joinen. 3. Text-/Ping-Nachrichten zwischen zwei Clients senden. 4. Run-Start zwischen zwei Clients synchronisieren. diff --git a/docs/setup.md b/docs/setup.md index d691bdd..636bf8b 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -1,6 +1,6 @@ # Brogether Setup -Stand: 2026-05-22 +Stand: 2026-05-23 ## Aktueller Zustand @@ -27,9 +27,11 @@ Brotato laedt in der Steam-Version nur Workshop-Ordner, deren ID Steam als abonn Danach Brotato ueber Steam starten. Die Mod sollte im Mod-Menue auftauchen. +Brogether selbst bleibt danach unsichtbar, bis es geoeffnet wird. Im Hauptmenue fuegt die Mod einen `BROGETHER`-Button hinzu. Alternativ oeffnet `F5` dasselbe Modal. Das Modal verhaelt sich bewusst wie Brotatos F8-Feedback-Fenster: es pausiert, legt sich ueber das Spiel und kann mit `Escape`, `F5` oder `Close` geschlossen werden. + ## Debug-Hotkeys im Spiel -- `F5`: Debug-Panel ein-/ausblenden +- `F5`: Brogether-Modal ein-/ausblenden - `F6`: oeffentliche Dev-Steam-Lobby erstellen - `F7`: Brogether-Lobby suchen und beitreten - `F9`: Lobby verlassen diff --git a/scripts/install-local.ps1 b/scripts/install-local.ps1 index 9e6bb05..8525712 100644 --- a/scripts/install-local.ps1 +++ b/scripts/install-local.ps1 @@ -38,4 +38,4 @@ Get-ChildItem -Path $legacyModsDir -Filter "PotatoConnect-PotatoConnect-*.zip" - Copy-Item -LiteralPath $zipPath -Destination (Join-Path $workshopItem.FullName (Split-Path -Leaf $zipPath)) -Force Write-Host "Installed to workshop item folder $($workshopItem.FullName)" -Write-Host "Start Brotato from Steam, then use F6 on host and F7 on client." +Write-Host "Start Brotato from Steam, then open BROGETHER in the main menu or press F5." diff --git a/src/mods-unpacked/Brogether-Brogether/extensions/main_ext.gd b/src/mods-unpacked/Brogether-Brogether/extensions/main_ext.gd index a68d9dc..454f332 100644 --- a/src/mods-unpacked/Brogether-Brogether/extensions/main_ext.gd +++ b/src/mods-unpacked/Brogether-Brogether/extensions/main_ext.gd @@ -35,10 +35,10 @@ func _exit_tree() -> void: func _get_brogether(): - var root := get_tree().get_root() + var root: Viewport = get_tree().get_root() if root == null: return null - var mod_loader := root.get_node_or_null("ModLoader") + var mod_loader: Node = root.get_node_or_null("ModLoader") if mod_loader == null: return null return mod_loader.get_node_or_null("Brogether-Brogether") diff --git a/src/mods-unpacked/Brogether-Brogether/extensions/main_menu_ext.gd b/src/mods-unpacked/Brogether-Brogether/extensions/main_menu_ext.gd new file mode 100644 index 0000000..f9d6dd2 --- /dev/null +++ b/src/mods-unpacked/Brogether-Brogether/extensions/main_menu_ext.gd @@ -0,0 +1,41 @@ +extends "res://ui/menus/pages/main_menu.gd" + +var _brogether_button: Button = null + + +func setup_menu() -> void: + .setup_menu() + _add_brogether_button() + + +func _add_brogether_button() -> void: + if _brogether_button != null and is_instance_valid(_brogether_button): + return + if right_container == null: + return + _brogether_button = Button.new() + _brogether_button.name = "BrogetherButton" + _brogether_button.text = "BROGETHER" + _brogether_button.rect_min_size = mods_button.rect_min_size + _brogether_button.size_flags_horizontal = mods_button.size_flags_horizontal + _brogether_button.size_flags_vertical = mods_button.size_flags_vertical + _brogether_button.theme = mods_button.theme + _brogether_button.connect("pressed", self, "_on_BrogetherButton_pressed") + right_container.add_child(_brogether_button) + right_container.move_child(_brogether_button, right_container.get_child_count() - 1) + + +func _on_BrogetherButton_pressed() -> void: + var brogether = _get_brogether() + if brogether != null: + brogether.open_debug_panel() + + +func _get_brogether(): + var root: Viewport = get_tree().get_root() + if root == null: + return null + var mod_loader: Node = root.get_node_or_null("ModLoader") + if mod_loader == null: + return null + return mod_loader.get_node_or_null("Brogether-Brogether") diff --git a/src/mods-unpacked/Brogether-Brogether/mod_main.gd b/src/mods-unpacked/Brogether-Brogether/mod_main.gd index 5f0246d..6e8af35 100644 --- a/src/mods-unpacked/Brogether-Brogether/mod_main.gd +++ b/src/mods-unpacked/Brogether-Brogether/mod_main.gd @@ -11,10 +11,14 @@ var is_host := false var packet_sequence := 0 var status_layer: CanvasLayer = null var status_label: Label = null +var dim_background: ColorRect = null var panel: PanelContainer = null var panel_status_label: Label = null var panel_log_label: Label = null +var panel_default_focus: Control = null var status_time_left := 0.0 +var restore_paused_value := false +var restore_focus_control = null var current_run_id := "" var current_run_active := false var current_players := [] @@ -23,6 +27,7 @@ var snapshot_sequence := 0 func _init() -> void: ModLoaderMod.install_script_extension("res://mods-unpacked/Brogether-Brogether/extensions/main_ext.gd") + ModLoaderMod.install_script_extension("res://mods-unpacked/Brogether-Brogether/extensions/main_menu_ext.gd") func _ready() -> void: name = MOD_ID @@ -33,7 +38,7 @@ func _ready() -> void: _create_status_overlay() _create_debug_panel() _init_steam() - _log("loaded. F5=toggle panel, F6=create lobby, F7=find/join lobby, F9=leave, F10=ping, F11=loopback test") + _log("loaded. F5=toggle modal, F6=create lobby, F7=find/join lobby, F9=leave, F10=ping, F11=loopback test") func _process(_delta: float) -> void: @@ -52,6 +57,10 @@ func _process(_delta: float) -> void: func _input(event: InputEvent) -> void: if not event is InputEventKey or not event.pressed or event.echo: return + if panel != null and panel.visible and event.scancode == KEY_ESCAPE: + close_debug_panel() + get_tree().set_input_as_handled() + return match event.scancode: KEY_F5: toggle_debug_panel() @@ -428,37 +437,48 @@ func _create_status_overlay() -> void: func _create_debug_panel() -> void: + dim_background = ColorRect.new() + dim_background.name = "BrogetherDimBackground" + dim_background.anchor_right = 1.0 + dim_background.anchor_bottom = 1.0 + dim_background.color = Color(0, 0, 0, 0.62) + dim_background.mouse_filter = Control.MOUSE_FILTER_STOP + dim_background.visible = false + status_layer.add_child(dim_background) + panel = PanelContainer.new() panel.name = "BrogetherPanel" - panel.anchor_left = 1.0 - panel.anchor_right = 1.0 - panel.anchor_top = 0.0 - panel.anchor_bottom = 0.0 - panel.margin_left = -280 - panel.margin_right = -16 - panel.margin_top = 54 - panel.margin_bottom = 286 + panel.anchor_left = 0.5 + panel.anchor_right = 0.5 + panel.anchor_top = 0.5 + panel.anchor_bottom = 0.5 + panel.margin_left = -260 + panel.margin_right = 260 + panel.margin_top = -170 + panel.margin_bottom = 170 panel.pause_mode = Node.PAUSE_MODE_PROCESS + panel.visible = false - var style := StyleBoxFlat.new() - style.bg_color = Color(0.05, 0.07, 0.08, 0.88) - style.border_color = Color(0.25, 1.0, 0.65, 0.9) + var style: StyleBoxFlat = StyleBoxFlat.new() + style.bg_color = Color(0.08, 0.09, 0.1, 0.96) + style.border_color = Color(0.55, 0.58, 0.62, 1.0) style.set_border_width_all(1) - style.set_corner_radius_all(4) - style.content_margin_left = 10 - style.content_margin_right = 10 - style.content_margin_top = 10 - style.content_margin_bottom = 10 + style.set_corner_radius_all(3) + style.content_margin_left = 18 + style.content_margin_right = 18 + style.content_margin_top = 16 + style.content_margin_bottom = 16 panel.add_stylebox_override("panel", style) - var box := VBoxContainer.new() + var box: VBoxContainer = VBoxContainer.new() box.name = "Content" - box.add_constant_override("separation", 6) + box.add_constant_override("separation", 10) panel.add_child(box) - var title := Label.new() - title.text = "Brogether" - title.add_color_override("font_color", Color(0.25, 1.0, 0.65, 1.0)) + var title: Label = Label.new() + title.text = "BROGETHER" + title.align = Label.ALIGN_CENTER + title.add_color_override("font_color", Color(1, 1, 1, 1)) box.add_child(title) panel_status_label = Label.new() @@ -466,50 +486,76 @@ func _create_debug_panel() -> void: panel_status_label.autowrap = true box.add_child(panel_status_label) - var grid := GridContainer.new() + var grid: GridContainer = GridContainer.new() grid.columns = 2 grid.add_constant_override("hseparation", 6) grid.add_constant_override("vseparation", 6) box.add_child(grid) - _add_panel_button(grid, "Host", "create_lobby") + panel_default_focus = _add_panel_button(grid, "Host", "create_lobby") _add_panel_button(grid, "Join", "find_lobbies") _add_panel_button(grid, "Leave", "leave_lobby") _add_panel_button(grid, "Ping", "send_ping") _add_panel_button(grid, "Loopback", "run_loopback_test") - _add_panel_button(grid, "Hide F5", "toggle_debug_panel") + _add_panel_button(grid, "Close", "close_debug_panel") panel_log_label = Label.new() panel_log_label.text = "Ready" panel_log_label.autowrap = true - panel_log_label.rect_min_size = Vector2(240, 46) + panel_log_label.rect_min_size = Vector2(480, 78) box.add_child(panel_log_label) status_layer.add_child(panel) _update_debug_panel() -func _add_panel_button(parent: Control, label: String, method_name: String) -> void: - var button := Button.new() +func _add_panel_button(parent: Control, label: String, method_name: String) -> Button: + var button: Button = Button.new() button.text = label - button.rect_min_size = Vector2(118, 30) - button.focus_mode = Control.FOCUS_NONE + button.rect_min_size = Vector2(236, 34) button.connect("pressed", self, method_name) parent.add_child(button) + return button func toggle_debug_panel() -> void: + if panel != null and panel.visible: + close_debug_panel() + else: + open_debug_panel() + + +func open_debug_panel() -> void: if panel == null: return - panel.visible = not panel.visible - _show_status("debug panel %s" % ("shown" if panel.visible else "hidden")) + restore_paused_value = get_tree().paused + restore_focus_control = _find_focus_owner() + get_tree().paused = true + Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) + dim_background.visible = true + panel.visible = true + set_process_input(true) + if panel_default_focus != null and is_instance_valid(panel_default_focus): + panel_default_focus.call_deferred("grab_focus") + _update_debug_panel("Panel opened") + + +func close_debug_panel() -> void: + if panel == null: + return + panel.visible = false + if dim_background != null: + dim_background.visible = false + get_tree().paused = restore_paused_value + if restore_focus_control != null and is_instance_valid(restore_focus_control): + restore_focus_control.call_deferred("grab_focus") func _update_debug_panel(last_message: String = "") -> void: if panel_status_label == null: return - var lobby_text := "none" if lobby_id == 0 else str(lobby_id) - var role := "host" if is_host else "client" + var lobby_text: String = "none" if lobby_id == 0 else str(lobby_id) + var role: String = "host" if is_host else "client" panel_status_label.text = "Steam %s\nLobby %s\nRole %s | Members %s" % [steam_id, lobby_text, role, lobby_members.size()] if panel_log_label != null and not last_message.empty(): panel_log_label.text = last_message @@ -518,11 +564,20 @@ func _update_debug_panel(last_message: String = "") -> void: func _show_status(message: String) -> void: if status_label == null: return + if panel == null or not panel.visible: + return status_label.text = "Brogether: " + message status_label.visible = true status_time_left = 5.0 +func _find_focus_owner(): + var scene: Node = get_tree().current_scene + if scene != null and scene.has_method("get_focus_owner"): + return scene.get_focus_owner() + return null + + func _build_run_state() -> Dictionary: return { "run_id": current_run_id,