feat(mvp): complete M13 clean-up milestone (real usernames, avatar studio redesign, pause menu, audio manager)
checkpoint / checkpoint (push) Successful in 6m22s
checkpoint / checkpoint (push) Successful in 6m22s
This commit is contained in:
+175
-25
@@ -36,11 +36,15 @@ 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).
|
||||
const ReportModal = preload("res://scripts/report_modal.gd")
|
||||
const PauseMenu = preload("res://scripts/pause_menu.gd")
|
||||
const ControlsModal = preload("res://scripts/controls_modal.gd")
|
||||
|
||||
var _build_editor: InGameEditor = null
|
||||
var _chat_overlay: Control = null
|
||||
var _friends_overlay: Control = null
|
||||
var _report_modal: Control = null
|
||||
var _pause_menu: Control = null
|
||||
var _controls_modal: Control = null
|
||||
var _hud_layer: CanvasLayer = null
|
||||
var _overlay_layer: CanvasLayer = null
|
||||
|
||||
@@ -51,13 +55,13 @@ var _loaded_chunk_coords: Array[Vector3i] = []
|
||||
|
||||
func _ready() -> void:
|
||||
_show_build_stamp()
|
||||
_setup_in_game_hud()
|
||||
_setup_chunk_streaming()
|
||||
if Session.editing:
|
||||
_open_editor_for_world()
|
||||
else:
|
||||
_setup_in_game_hud()
|
||||
if Session.active:
|
||||
_setup_networked()
|
||||
_setup_chunk_streaming()
|
||||
|
||||
|
||||
func _setup_chunk_streaming() -> void:
|
||||
@@ -175,26 +179,22 @@ 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)):
|
||||
if not (_build_editor != null and is_instance_valid(_build_editor)) and not (_pause_menu != null and is_instance_valid(_pause_menu)):
|
||||
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)):
|
||||
if not (_build_editor != null and is_instance_valid(_build_editor)) and not (_pause_menu != null and is_instance_valid(_pause_menu)):
|
||||
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
|
||||
# scratch/in-session build mode's own toggle and doesn't apply.
|
||||
if not Session.editing:
|
||||
get_viewport().set_input_as_handled()
|
||||
_toggle_build_mode()
|
||||
get_viewport().set_input_as_handled()
|
||||
_toggle_build_mode()
|
||||
return
|
||||
|
||||
# ESC leaves the world back to the browser (or closes an open overlay/editor).
|
||||
# ESC opens Pause Menu (or closes open modal/overlay/editor).
|
||||
if event.is_action_pressed("ui_cancel"):
|
||||
get_viewport().set_input_as_handled()
|
||||
if _report_modal != null and is_instance_valid(_report_modal):
|
||||
@@ -203,21 +203,19 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
_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()
|
||||
elif _controls_modal != null and is_instance_valid(_controls_modal):
|
||||
_close_controls_modal()
|
||||
elif _pause_menu != null and is_instance_valid(_pause_menu):
|
||||
_close_pause_menu()
|
||||
elif _build_editor != null and is_instance_valid(_build_editor):
|
||||
_close_build_mode()
|
||||
else:
|
||||
_leave_world()
|
||||
_open_pause_menu()
|
||||
|
||||
|
||||
# ─── 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()
|
||||
@@ -274,13 +272,69 @@ func _setup_in_game_hud() -> void:
|
||||
_close_chat_overlay()
|
||||
elif _friends_overlay != null and is_instance_valid(_friends_overlay):
|
||||
_close_friends_overlay()
|
||||
elif _controls_modal != null and is_instance_valid(_controls_modal):
|
||||
_close_controls_modal()
|
||||
elif _pause_menu != null and is_instance_valid(_pause_menu):
|
||||
_close_pause_menu()
|
||||
elif _build_editor != null and is_instance_valid(_build_editor):
|
||||
_close_build_mode()
|
||||
else:
|
||||
_leave_world()
|
||||
_open_pause_menu()
|
||||
)
|
||||
bar.add_child(exit_btn)
|
||||
|
||||
# Bottom controls pill hint
|
||||
var controls_pill := PanelContainer.new()
|
||||
controls_pill.name = "ControlsPill"
|
||||
var pill_style := StyleBoxFlat.new()
|
||||
pill_style.bg_color = Color(0.08, 0.10, 0.14, 0.75)
|
||||
pill_style.set_corner_radius_all(14)
|
||||
pill_style.content_margin_left = 14
|
||||
pill_style.content_margin_right = 14
|
||||
pill_style.content_margin_top = 4
|
||||
pill_style.content_margin_bottom = 4
|
||||
controls_pill.add_theme_stylebox_override("panel", pill_style)
|
||||
controls_pill.set_anchors_preset(Control.PRESET_BOTTOM_WIDE)
|
||||
controls_pill.offset_top = -34
|
||||
controls_pill.offset_bottom = -8
|
||||
controls_pill.offset_left = 220
|
||||
controls_pill.offset_right = -220
|
||||
|
||||
var hint_lbl := Label.new()
|
||||
hint_lbl.text = "[W][A][S][D] Move [Space] Jump [E] Interact [F1] Friends [T] Chat [F2] Build [ESC] Menu"
|
||||
hint_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
hint_lbl.add_theme_font_size_override("font_size", 12)
|
||||
hint_lbl.add_theme_color_override("font_color", Color(0.85, 0.9, 0.95, 0.85))
|
||||
controls_pill.add_child(hint_lbl)
|
||||
_hud_layer.add_child(controls_pill)
|
||||
|
||||
# If editing in creator test mode, show status badge
|
||||
if Session.editing and Session.world_id != null and int(Session.world_id) > 0:
|
||||
var creator_pill := PanelContainer.new()
|
||||
creator_pill.name = "CreatorTestPill"
|
||||
var c_style := StyleBoxFlat.new()
|
||||
c_style.bg_color = Color(0.18, 0.14, 0.04, 0.85)
|
||||
c_style.border_color = Color(0.9, 0.75, 0.2, 0.9)
|
||||
c_style.set_border_width_all(1)
|
||||
c_style.set_corner_radius_all(6)
|
||||
c_style.content_margin_left = 10
|
||||
c_style.content_margin_right = 10
|
||||
c_style.content_margin_top = 4
|
||||
c_style.content_margin_bottom = 4
|
||||
creator_pill.add_theme_stylebox_override("panel", c_style)
|
||||
creator_pill.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||||
creator_pill.offset_left = -280
|
||||
creator_pill.offset_top = 16
|
||||
creator_pill.offset_right = -20
|
||||
creator_pill.offset_bottom = 46
|
||||
|
||||
var creator_lbl := Label.new()
|
||||
creator_lbl.text = "🛠️ Creator Test Mode (World #%s)" % str(Session.world_id)
|
||||
creator_lbl.add_theme_font_size_override("font_size", 12)
|
||||
creator_lbl.add_theme_color_override("font_color", Color(1.0, 0.9, 0.4))
|
||||
creator_pill.add_child(creator_lbl)
|
||||
_hud_layer.add_child(creator_pill)
|
||||
|
||||
|
||||
func _toggle_report_modal() -> void:
|
||||
if _report_modal != null and is_instance_valid(_report_modal):
|
||||
@@ -451,6 +505,90 @@ func _close_friends_overlay() -> void:
|
||||
_player._refresh_mouse_mode()
|
||||
|
||||
|
||||
func _toggle_pause_menu() -> void:
|
||||
if _pause_menu != null and is_instance_valid(_pause_menu):
|
||||
_close_pause_menu()
|
||||
else:
|
||||
_open_pause_menu()
|
||||
|
||||
|
||||
func _open_pause_menu() -> 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()
|
||||
if _controls_modal != null and is_instance_valid(_controls_modal):
|
||||
_close_controls_modal()
|
||||
|
||||
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 can_edit := Session.editing or (Session.world_id != null and int(Session.world_id) > 0)
|
||||
var title_str := "World #%s" % str(Session.world_id) if Session.world_id != null and int(Session.world_id) > 0 else "Sandbox World"
|
||||
_pause_menu = PauseMenu.new(can_edit, title_str)
|
||||
_pause_menu.resume_requested.connect(_close_pause_menu)
|
||||
_pause_menu.edit_world_requested.connect(func():
|
||||
_close_pause_menu()
|
||||
_open_build_mode()
|
||||
)
|
||||
_pause_menu.friends_requested.connect(func():
|
||||
_close_pause_menu()
|
||||
_open_friends_overlay()
|
||||
)
|
||||
_pause_menu.chat_requested.connect(func():
|
||||
_close_pause_menu()
|
||||
_open_chat_overlay()
|
||||
)
|
||||
_pause_menu.controls_requested.connect(func():
|
||||
_open_controls_modal()
|
||||
)
|
||||
_pause_menu.leave_world_confirmed.connect(func():
|
||||
_close_pause_menu()
|
||||
_leave_world()
|
||||
)
|
||||
_overlay_layer.add_child(_pause_menu)
|
||||
|
||||
|
||||
func _close_pause_menu() -> void:
|
||||
if _pause_menu != null and is_instance_valid(_pause_menu):
|
||||
_pause_menu.queue_free()
|
||||
_pause_menu = null
|
||||
if _controls_modal != null and is_instance_valid(_controls_modal):
|
||||
_controls_modal.queue_free()
|
||||
_controls_modal = 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 _open_controls_modal() -> void:
|
||||
if _controls_modal != null and is_instance_valid(_controls_modal):
|
||||
return
|
||||
_controls_modal = ControlsModal.new()
|
||||
_controls_modal.closed.connect(_close_controls_modal)
|
||||
if _overlay_layer != null and is_instance_valid(_overlay_layer):
|
||||
_overlay_layer.add_child(_controls_modal)
|
||||
else:
|
||||
add_child(_controls_modal)
|
||||
|
||||
|
||||
func _close_controls_modal() -> void:
|
||||
if _controls_modal != null and is_instance_valid(_controls_modal):
|
||||
_controls_modal.queue_free()
|
||||
_controls_modal = null
|
||||
|
||||
|
||||
## 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
|
||||
@@ -507,6 +645,7 @@ func _open_build_mode() -> void:
|
||||
_build_editor.setup_persistence(save_service, world_id_str, room_slug, coord, base_revision)
|
||||
|
||||
_build_editor.closed.connect(_close_build_mode)
|
||||
_build_editor.exit_browser_requested.connect(_exit_editor_to_browser)
|
||||
add_child(_build_editor)
|
||||
|
||||
|
||||
@@ -578,7 +717,8 @@ func _open_editor_for_world() -> void:
|
||||
|
||||
_build_editor = InGameEditor.new(core)
|
||||
_build_editor.setup_persistence(save_service, world_id_str, room_slug, coord, base_revision)
|
||||
_build_editor.closed.connect(_exit_editor_to_browser)
|
||||
_build_editor.closed.connect(_close_build_mode)
|
||||
_build_editor.exit_browser_requested.connect(_exit_editor_to_browser)
|
||||
add_child(_build_editor)
|
||||
|
||||
|
||||
@@ -728,13 +868,23 @@ func _sync_remotes(states: Dictionary, delta: float) -> void:
|
||||
var st: Dictionary = states[pid]
|
||||
var node: Node3D = _remotes.get(pid)
|
||||
var loadout: Variant = st.get("loadout", null)
|
||||
var uname: String = str(st.get("username", ""))
|
||||
if uname.is_empty() and loadout is Dictionary:
|
||||
uname = str(loadout.get("username", ""))
|
||||
if uname.is_empty():
|
||||
uname = "Player %d" % pid
|
||||
|
||||
if node == null:
|
||||
node = _make_remote(pid, loadout)
|
||||
node = _make_remote(pid, loadout, uname)
|
||||
_remotes[pid] = node
|
||||
add_child(node)
|
||||
node.global_position = st.get("position", Vector3.ZERO)
|
||||
elif loadout is Dictionary and not (loadout as Dictionary).is_empty() and _remote_loadouts.get(pid) != loadout:
|
||||
_setup_remote_visual(node, pid, loadout)
|
||||
else:
|
||||
if loadout is Dictionary and not (loadout as Dictionary).is_empty() and _remote_loadouts.get(pid) != loadout:
|
||||
_setup_remote_visual(node, pid, loadout)
|
||||
var tag := node.get_node_or_null("NameTag") as Label3D
|
||||
if tag != null and tag.text != uname and not uname.is_empty():
|
||||
tag.text = uname
|
||||
|
||||
var target: Vector3 = st.get("position", node.global_position)
|
||||
node.global_position = node.global_position.lerp(target, clampf(REMOTE_LERP * delta, 0.0, 1.0))
|
||||
@@ -756,7 +906,7 @@ func _sync_remotes(states: Dictionary, delta: float) -> void:
|
||||
_remote_loadouts.erase(pid)
|
||||
|
||||
|
||||
func _make_remote(pid: int, loadout: Variant = null) -> Node3D:
|
||||
func _make_remote(pid: int, loadout: Variant = null, uname: String = "") -> Node3D:
|
||||
var root := Node3D.new()
|
||||
root.name = "Remote_%d" % pid
|
||||
|
||||
@@ -764,7 +914,7 @@ func _make_remote(pid: int, loadout: Variant = null) -> Node3D:
|
||||
|
||||
var tag := Label3D.new()
|
||||
tag.name = "NameTag"
|
||||
tag.text = "Player %d" % pid
|
||||
tag.text = uname if not uname.is_empty() else ("Player %d" % pid)
|
||||
tag.position = Vector3(0, 2.1, 0)
|
||||
tag.billboard = BaseMaterial3D.BILLBOARD_ENABLED
|
||||
tag.no_depth_test = true
|
||||
|
||||
Reference in New Issue
Block a user