feat(ui): wire M8 social, M9 economy, and M10 discovery into main menu and in-game HUD
checkpoint / checkpoint (push) Successful in 5m54s

This commit is contained in:
2026-09-17 18:04:47 +00:00
parent ea6948a9ba
commit e05e18c384
10 changed files with 382 additions and 23 deletions
+216 -6
View File
@@ -36,6 +36,10 @@ var _status_label: Label = null
## normal play"). Editing is in-memory only here; the save path to the
## backend is a sibling task (world_persistence_impl.md).
var _build_editor: InGameEditor = null
var _chat_overlay: Control = null
var _friends_overlay: Control = null
var _hud_layer: CanvasLayer = null
var _overlay_layer: CanvasLayer = null
var _stream_manager: ChunkStreamManager = null
@@ -47,6 +51,7 @@ func _ready() -> void:
if Session.editing:
_open_editor_for_world()
else:
_setup_in_game_hud()
if Session.active:
_setup_networked()
_setup_chunk_streaming()
@@ -165,6 +170,18 @@ func _hide_default_test_props() -> void:
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed and not event.echo:
if event.keycode == KEY_T or (event.keycode == KEY_ENTER and not (_chat_overlay != null and is_instance_valid(_chat_overlay))):
if not Session.editing and not (_build_editor != null and is_instance_valid(_build_editor)):
get_viewport().set_input_as_handled()
_toggle_chat_overlay()
return
elif event.keycode == KEY_TAB or event.keycode == KEY_F1:
if not Session.editing and not (_build_editor != null and is_instance_valid(_build_editor)):
get_viewport().set_input_as_handled()
_toggle_friends_overlay()
return
if event.is_action_pressed("toggle_build_mode"):
# world_persistence_impl.md §6 D4: the non-joined "Edit world" entry
# already has its editor open from _ready() -- F2 here is the
@@ -174,14 +191,14 @@ func _unhandled_input(event: InputEvent) -> void:
_toggle_build_mode()
return
# ESC leaves the world back to the browser (or, while build mode is
# open, just closes build mode instead). Handled here (the scene root
# that owns the connection) rather than in player.gd so the teardown is
# a single ordered path -- doing it from two places raced _on_server_lost
# against the scene change and looked like a crash.
# ESC leaves the world back to the browser (or closes an open overlay/editor).
if event.is_action_pressed("ui_cancel"):
get_viewport().set_input_as_handled()
if Session.editing:
if _chat_overlay != null and is_instance_valid(_chat_overlay):
_close_chat_overlay()
elif _friends_overlay != null and is_instance_valid(_friends_overlay):
_close_friends_overlay()
elif Session.editing:
# Non-joined editing has no play session to fall back into --
# closing it means leaving, same as the Close button.
_exit_editor_to_browser()
@@ -191,6 +208,194 @@ func _unhandled_input(event: InputEvent) -> void:
_leave_world()
# ─── In-Game HUD & Social Overlays ───────────────────────────────────────────
func _setup_in_game_hud() -> void:
if Session.editing:
return
if _hud_layer != null and is_instance_valid(_hud_layer):
return
_hud_layer = CanvasLayer.new()
_hud_layer.name = "InGameHUD"
_hud_layer.layer = 10
add_child(_hud_layer)
var panel := PanelContainer.new()
panel.name = "HUDPanel"
panel.position = Vector2(20, 20)
var style := StyleBoxFlat.new()
style.bg_color = Color(0.1, 0.12, 0.16, 0.8)
style.border_color = Color(0.3, 0.35, 0.45, 0.8)
style.set_border_width_all(1)
style.set_corner_radius_all(6)
style.content_margin_left = 8
style.content_margin_right = 8
style.content_margin_top = 6
style.content_margin_bottom = 6
panel.add_theme_stylebox_override("panel", style)
_hud_layer.add_child(panel)
var bar := HBoxContainer.new()
bar.name = "QuickActionBar"
bar.add_theme_constant_override("separation", 8)
panel.add_child(bar)
var chat_btn := Button.new()
chat_btn.text = "💬 Chat [T]"
chat_btn.pressed.connect(_toggle_chat_overlay)
bar.add_child(chat_btn)
var friends_btn := Button.new()
friends_btn.text = "👥 Friends [Tab]"
friends_btn.pressed.connect(_toggle_friends_overlay)
bar.add_child(friends_btn)
var build_btn := Button.new()
build_btn.text = "🔨 Build [F2]"
build_btn.pressed.connect(_toggle_build_mode)
bar.add_child(build_btn)
var exit_btn := Button.new()
exit_btn.text = "🚪 Menu [ESC]"
exit_btn.pressed.connect(func():
if _chat_overlay != null and is_instance_valid(_chat_overlay):
_close_chat_overlay()
elif _friends_overlay != null and is_instance_valid(_friends_overlay):
_close_friends_overlay()
elif _build_editor != null and is_instance_valid(_build_editor):
_close_build_mode()
else:
_leave_world()
)
bar.add_child(exit_btn)
func _toggle_chat_overlay() -> void:
if _chat_overlay != null and is_instance_valid(_chat_overlay):
_close_chat_overlay()
else:
_open_chat_overlay()
func _open_chat_overlay() -> void:
if _friends_overlay != null and is_instance_valid(_friends_overlay):
_close_friends_overlay()
if _build_editor != null and is_instance_valid(_build_editor):
_close_build_mode()
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
_player.set_physics_process(false)
_player.set_process_unhandled_input(false)
if _overlay_layer == null or not is_instance_valid(_overlay_layer):
_overlay_layer = CanvasLayer.new()
_overlay_layer.name = "OverlayLayer"
_overlay_layer.layer = 20
add_child(_overlay_layer)
var bg := ColorRect.new()
bg.name = "OverlayBG"
bg.color = Color(0.06, 0.08, 0.12, 0.9)
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
_overlay_layer.add_child(bg)
var chat_scene := load("res://scenes/chat_ui.tscn")
_chat_overlay = chat_scene.instantiate() as Control
_chat_overlay.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.add_child(_chat_overlay)
# If active in a world, configure world chat automatically
if Session.world_id != null and int(Session.world_id) > 0:
var wid_edit = _chat_overlay.find_child("WorldIdEdit", true, false) as LineEdit
if wid_edit != null:
wid_edit.text = str(Session.world_id)
if _chat_overlay.has_method("switch_world_chat"):
_chat_overlay.switch_world_chat(int(Session.world_id))
if _chat_overlay.has_signal("back_requested"):
_chat_overlay.back_requested.connect(_close_chat_overlay)
var back_btn = _chat_overlay.find_child("BackButton", true, false) as Button
if back_btn != null:
back_btn.text = "✕ Close Chat"
if not back_btn.pressed.is_connected(_close_chat_overlay):
back_btn.pressed.connect(_close_chat_overlay)
var msg_input = _chat_overlay.find_child("MessageInput", true, false) as LineEdit
if msg_input != null:
msg_input.grab_focus.call_deferred()
func _close_chat_overlay() -> void:
if _chat_overlay != null and is_instance_valid(_chat_overlay):
_chat_overlay.queue_free()
_chat_overlay = null
if _overlay_layer != null and is_instance_valid(_overlay_layer):
_overlay_layer.queue_free()
_overlay_layer = null
_player.set_physics_process(true)
_player.set_process_unhandled_input(true)
_player._refresh_mouse_mode()
func _toggle_friends_overlay() -> void:
if _friends_overlay != null and is_instance_valid(_friends_overlay):
_close_friends_overlay()
else:
_open_friends_overlay()
func _open_friends_overlay() -> void:
if _chat_overlay != null and is_instance_valid(_chat_overlay):
_close_chat_overlay()
if _build_editor != null and is_instance_valid(_build_editor):
_close_build_mode()
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
_player.set_physics_process(false)
_player.set_process_unhandled_input(false)
if _overlay_layer == null or not is_instance_valid(_overlay_layer):
_overlay_layer = CanvasLayer.new()
_overlay_layer.name = "OverlayLayer"
_overlay_layer.layer = 20
add_child(_overlay_layer)
var bg := ColorRect.new()
bg.name = "OverlayBG"
bg.color = Color(0.06, 0.08, 0.12, 0.9)
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
_overlay_layer.add_child(bg)
var friends_scene := load("res://scenes/friends_presence_ui.tscn")
_friends_overlay = friends_scene.instantiate() as Control
_friends_overlay.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.add_child(_friends_overlay)
if _friends_overlay.has_signal("back_requested"):
_friends_overlay.back_requested.connect(_close_friends_overlay)
var back_btn = _friends_overlay.find_child("BackButton", true, false) as Button
if back_btn != null:
back_btn.text = "✕ Close"
if not back_btn.pressed.is_connected(_close_friends_overlay):
back_btn.pressed.connect(_close_friends_overlay)
func _close_friends_overlay() -> void:
if _friends_overlay != null and is_instance_valid(_friends_overlay):
_friends_overlay.queue_free()
_friends_overlay = null
if _overlay_layer != null and is_instance_valid(_overlay_layer):
_overlay_layer.queue_free()
_overlay_layer = null
_player.set_physics_process(true)
_player.set_process_unhandled_input(true)
_player._refresh_mouse_mode()
## Entry point into the in-game editor (docs/architecture/editor.md §7.2).
## Freezes the player and opens a fresh, empty in-memory chunk to edit --
## loading a *real* chunk from the backend for editing is the sibling
@@ -204,6 +409,11 @@ func _toggle_build_mode() -> void:
func _open_build_mode() -> void:
if _chat_overlay != null and is_instance_valid(_chat_overlay):
_close_chat_overlay()
if _friends_overlay != null and is_instance_valid(_friends_overlay):
_close_friends_overlay()
var world_id_str := str(Session.world_id) if Session.world_id != null else "0"
var room_slug := "main"
var coord := Vector3i.ZERO