feat(ui): wire M8 social, M9 economy, and M10 discovery into main menu and in-game HUD
checkpoint / checkpoint (push) Successful in 5m54s
checkpoint / checkpoint (push) Successful in 5m54s
This commit is contained in:
@@ -10,6 +10,7 @@ signal worlds_loaded(count: int)
|
||||
|
||||
@onready var back_button: Button = $VBox/Header/BackButton
|
||||
@onready var refresh_button: Button = $VBox/Header/RefreshButton
|
||||
@onready var world_list_button: Button = $VBox/Header.get_node_or_null("WorldListButton")
|
||||
@onready var status_label: Label = $VBox/StatusLabel
|
||||
|
||||
# Filter Controls
|
||||
@@ -94,6 +95,8 @@ func _wire_signals() -> void:
|
||||
prev_button.pressed.connect(_on_prev_page_pressed)
|
||||
if next_button and not next_button.pressed.is_connected(_on_next_page_pressed):
|
||||
next_button.pressed.connect(_on_next_page_pressed)
|
||||
if world_list_button and not world_list_button.pressed.is_connected(_on_world_list_pressed):
|
||||
world_list_button.pressed.connect(_on_world_list_pressed)
|
||||
|
||||
|
||||
func load_categories() -> void:
|
||||
@@ -372,5 +375,10 @@ func _on_next_page_pressed() -> void:
|
||||
|
||||
func _on_back_pressed() -> void:
|
||||
back_requested.emit()
|
||||
if get_tree():
|
||||
if get_tree() and get_tree().current_scene == self:
|
||||
get_tree().change_scene_to_file("res://scenes/main_menu.tscn")
|
||||
|
||||
|
||||
func _on_world_list_pressed() -> void:
|
||||
if get_tree():
|
||||
get_tree().change_scene_to_file("res://scenes/world_list.tscn")
|
||||
|
||||
+216
-6
@@ -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
|
||||
|
||||
+72
-12
@@ -39,8 +39,15 @@ var _dob_edit: LineEdit
|
||||
var _auth_button: Button
|
||||
var _status_label: Label
|
||||
var _welcome_label: Label
|
||||
var _discover_button: Button
|
||||
var _play_button: Button
|
||||
var _customize_button: Button
|
||||
var _social_button: Button
|
||||
var _chat_button: Button
|
||||
var _marketplace_button: Button
|
||||
var _wallet_button: Button
|
||||
var _groups_button: Button
|
||||
var _selling_button: Button
|
||||
var _quit_button: Button
|
||||
|
||||
var _is_register_mode: bool = false
|
||||
@@ -93,16 +100,22 @@ func _ready() -> void:
|
||||
_auth_button = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/AuthButton
|
||||
_status_label = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/AuthStatusLabel
|
||||
_welcome_label = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/WelcomeLabel
|
||||
_discover_button = $CenterContainer/VBoxContainer.get_node_or_null("DiscoverButton")
|
||||
_play_button = $CenterContainer/VBoxContainer/PlayButton
|
||||
_customize_button = $CenterContainer/VBoxContainer/CustomizeButton
|
||||
_social_button = $CenterContainer/VBoxContainer.get_node_or_null("SocialButton")
|
||||
_chat_button = $CenterContainer/VBoxContainer.get_node_or_null("ChatButton")
|
||||
_marketplace_button = $CenterContainer/VBoxContainer.get_node_or_null("MarketplaceButton")
|
||||
_wallet_button = $CenterContainer/VBoxContainer.get_node_or_null("WalletButton")
|
||||
_groups_button = $CenterContainer/VBoxContainer.get_node_or_null("GroupsButton")
|
||||
_selling_button = $CenterContainer/VBoxContainer.get_node_or_null("SellingButton")
|
||||
_quit_button = $CenterContainer/VBoxContainer/QuitButton
|
||||
|
||||
# Default to Play being gated. When a token exists, the async session
|
||||
# Default to navigation being gated. When a token exists, the async session
|
||||
# check below may take several frames (a real get_me() round-trip);
|
||||
# keeping the button disabled until validation succeeds prevents a fast
|
||||
# click from navigating to world_list with an expired/invalid token.
|
||||
_play_button.disabled = true
|
||||
_customize_button.disabled = true
|
||||
_set_nav_enabled(false)
|
||||
|
||||
# Check whether a valid session already exists (e.g. user logged in,
|
||||
# navigated to world_list, then clicked Back). If so, restore the
|
||||
@@ -122,6 +135,16 @@ func _ready() -> void:
|
||||
auth_state_ready.emit()
|
||||
|
||||
|
||||
func _set_nav_enabled(enabled: bool) -> void:
|
||||
var buttons = [
|
||||
_discover_button, _play_button, _customize_button, _social_button,
|
||||
_chat_button, _marketplace_button, _wallet_button, _groups_button, _selling_button
|
||||
]
|
||||
for b in buttons:
|
||||
if b != null and is_instance_valid(b):
|
||||
b.disabled = not enabled
|
||||
|
||||
|
||||
## Restore the UI to the authenticated state (Play enabled, welcome message
|
||||
## visible, auth buttons hidden). AuthPanel itself stays visible -- the
|
||||
## welcome label lives inside it, so hiding the panel hides the label too
|
||||
@@ -148,8 +171,7 @@ func _show_authenticated(username: String) -> void:
|
||||
_dob_edit.visible = false
|
||||
_auth_button.visible = false
|
||||
_auth_mode_label.visible = false
|
||||
_play_button.disabled = false
|
||||
_customize_button.disabled = false
|
||||
_set_nav_enabled(true)
|
||||
_auth_buttons_row.visible = false
|
||||
print("Restored authenticated session for: ", username)
|
||||
|
||||
@@ -163,18 +185,21 @@ func _reset_to_logged_out() -> void:
|
||||
_email_edit.visible = false
|
||||
_dob_edit.visible = false
|
||||
_dob_edit.text = ""
|
||||
_play_button.disabled = true # Gate play behind successful auth.
|
||||
_customize_button.disabled = true
|
||||
_set_nav_enabled(false)
|
||||
_update_auth_mode_ui()
|
||||
|
||||
|
||||
# ─── Play / Quit ──────────────────────────────────────────────────────────────
|
||||
# ─── Navigation ───────────────────────────────────────────────────────────────
|
||||
|
||||
func _on_discover_button_pressed() -> void:
|
||||
if _discover_button != null and _discover_button.disabled:
|
||||
return
|
||||
get_tree().change_scene_to_file("res://scenes/discovery_ui.tscn")
|
||||
|
||||
|
||||
func _on_play_button_pressed() -> void:
|
||||
if _play_button.disabled:
|
||||
return
|
||||
# Browse worlds is the first real screen past login -- actual gameplay
|
||||
# (joining a world) doesn't exist yet, browsing does.
|
||||
get_tree().change_scene_to_file("res://scenes/world_list.tscn")
|
||||
|
||||
|
||||
@@ -184,6 +209,42 @@ func _on_customize_button_pressed() -> void:
|
||||
get_tree().change_scene_to_file("res://scenes/avatar_customization.tscn")
|
||||
|
||||
|
||||
func _on_social_button_pressed() -> void:
|
||||
if _social_button != null and _social_button.disabled:
|
||||
return
|
||||
get_tree().change_scene_to_file("res://scenes/friends_presence_ui.tscn")
|
||||
|
||||
|
||||
func _on_chat_button_pressed() -> void:
|
||||
if _chat_button != null and _chat_button.disabled:
|
||||
return
|
||||
get_tree().change_scene_to_file("res://scenes/chat_ui.tscn")
|
||||
|
||||
|
||||
func _on_marketplace_button_pressed() -> void:
|
||||
if _marketplace_button != null and _marketplace_button.disabled:
|
||||
return
|
||||
get_tree().change_scene_to_file("res://scenes/marketplace_ui.tscn")
|
||||
|
||||
|
||||
func _on_wallet_button_pressed() -> void:
|
||||
if _wallet_button != null and _wallet_button.disabled:
|
||||
return
|
||||
get_tree().change_scene_to_file("res://scenes/wallet_ui.tscn")
|
||||
|
||||
|
||||
func _on_groups_button_pressed() -> void:
|
||||
if _groups_button != null and _groups_button.disabled:
|
||||
return
|
||||
get_tree().change_scene_to_file("res://scenes/groups_ui.tscn")
|
||||
|
||||
|
||||
func _on_selling_button_pressed() -> void:
|
||||
if _selling_button != null and _selling_button.disabled:
|
||||
return
|
||||
get_tree().change_scene_to_file("res://scenes/selling_payout_ui.tscn")
|
||||
|
||||
|
||||
func _on_quit_button_pressed() -> void:
|
||||
get_tree().quit()
|
||||
|
||||
@@ -301,8 +362,7 @@ func _on_auth_complete(result: Dictionary) -> void:
|
||||
_status_label.visible = false
|
||||
_welcome_label.text = "Welcome, %s!" % _username_edit.text.strip_edges()
|
||||
_welcome_label.visible = true
|
||||
_play_button.disabled = false
|
||||
_customize_button.disabled = false
|
||||
_set_nav_enabled(true)
|
||||
_auth_buttons_row.visible = false
|
||||
print("Auth successful. Token stored.")
|
||||
await _prime_avatar()
|
||||
|
||||
@@ -69,6 +69,8 @@ func _wire_signals() -> void:
|
||||
|
||||
func _on_back_pressed() -> void:
|
||||
back_requested.emit()
|
||||
if get_tree() and get_tree().current_scene == self:
|
||||
get_tree().change_scene_to_file("res://scenes/main_menu.tscn")
|
||||
|
||||
|
||||
func show_status(text: String, is_error: bool = false) -> void:
|
||||
|
||||
@@ -72,6 +72,8 @@ func _wire_signals() -> void:
|
||||
|
||||
func _on_back_pressed() -> void:
|
||||
back_requested.emit()
|
||||
if get_tree() and get_tree().current_scene == self:
|
||||
get_tree().change_scene_to_file("res://scenes/main_menu.tscn")
|
||||
|
||||
|
||||
func show_status(text: String, is_error: bool = false) -> void:
|
||||
|
||||
@@ -68,6 +68,8 @@ func _wire_signals() -> void:
|
||||
|
||||
func _on_back_pressed() -> void:
|
||||
back_requested.emit()
|
||||
if get_tree() and get_tree().current_scene == self:
|
||||
get_tree().change_scene_to_file("res://scenes/main_menu.tscn")
|
||||
|
||||
|
||||
func show_status(text: String, is_error: bool = false) -> void:
|
||||
|
||||
@@ -201,6 +201,10 @@ func _on_back_button_pressed() -> void:
|
||||
get_tree().change_scene_to_file("res://scenes/main_menu.tscn")
|
||||
|
||||
|
||||
func _on_discover_button_pressed() -> void:
|
||||
get_tree().change_scene_to_file("res://scenes/discovery_ui.tscn")
|
||||
|
||||
|
||||
|
||||
## Fetch the world list from the backend and update the UI.
|
||||
func _refresh_worlds() -> void:
|
||||
|
||||
Reference in New Issue
Block a user