PC3 Phase 1.4-1.6: avatars wired into the game end to end
- api_client.gd: get_avatar_loadout() / save_avatar_loadout() - main_menu.gd: on successful auth (fresh or session-restore), load the catalog + fetch the saved loadout into AvatarSystem so the in-world player renders as the player's avatar. Best-effort: a slow/failed prime never gates getting in (UI goes authenticated first, prime runs after). New "Customize Avatar" button (enabled only when signed in). - avatar_system.gd: local-player convenience API -- ensure_catalog(), set_loadout_from_dict() (from the backend's GET shape), has_loadout(), build_current_avatar(). - player.gd: swaps the placeholder capsule for the assembled avatar (feet-aligned to the capsule bottom) when a loadout is primed; silent fallback to the capsule otherwise. Camera stays first-person for now (third-person is a noted follow-up). - avatar_visualizer.gd: rewrote it -- the old version referenced AvatarLoader.SlotResult / load_slot() which never existed, so it didn't compile and took avatar_customization_ui.gd down with it. Now a thin wrapper over AvatarLoader for the preview. - avatar_customization_ui.gd: seeds from the live loadout, saves through to AvatarSystem + PUT /api/avatar/loadout (local file kept as fallback), fixed a Camera3D.look_at() before-in-tree crash. - scenes/avatar_customization.tscn rebuilt (old one used the Godot-3 `ViewportContainer` node type) + avatar_customization_screen.gd wrapper that returns to the menu on close. - smoke_test_avatar_in_world.gd: primed loadout -> game.tscn -> player shows the assembled avatar, capsule hidden, rig + bone mounts present. Verified: cold-cache smokes green (player 15/15, avatar_system 14/14, avatar_in_world 10/10, main_menu_ui 13/13); against a live backend main_menu_full_flow 12/12, main_menu_back 20/20, world_list 4/4, enter_world 9/9, e2e 4/4; plus a manual register->default-loadout-> customize->save->re-fetch-persisted round trip. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
This commit is contained in:
@@ -106,9 +106,14 @@ func _ready() -> void:
|
||||
# Build the UI.
|
||||
_build_ui()
|
||||
|
||||
# Create a fresh loadout.
|
||||
loadout = AvatarLoadout.new()
|
||||
loadout.base_body = default_base_body
|
||||
# Seed from the player's live loadout (set at login) if there is one, so
|
||||
# the screen opens showing what they're actually wearing; otherwise start
|
||||
# from a bare default.
|
||||
if _system != null and _system.current_loadout != null:
|
||||
loadout = AvatarLoadout.from_json(_system.current_loadout.to_json())
|
||||
if loadout == null or not loadout.validate():
|
||||
loadout = AvatarLoadout.new()
|
||||
loadout.base_body = default_base_body
|
||||
|
||||
# Initialize the visualizer.
|
||||
_visualizer = AvatarVisualizer.new()
|
||||
@@ -165,8 +170,9 @@ func _build_ui() -> void:
|
||||
# Camera for the preview.
|
||||
_preview_camera = Camera3D.new()
|
||||
_preview_camera.name = "PreviewCamera"
|
||||
_preview_camera.position = Vector3(0, 1.0, 3.0)
|
||||
_preview_camera.look_at(Vector3(0, 1.0, 0))
|
||||
# look_at needs a global transform, which only exists once in the tree.
|
||||
_preview_camera.look_at_from_position(
|
||||
Vector3(0, 1.0, 3.0), Vector3(0, 1.0, 0), Vector3.UP)
|
||||
_preview_viewport.add_child(_preview_camera)
|
||||
|
||||
# Avatar root inside the viewport.
|
||||
@@ -429,14 +435,26 @@ func _on_skin_tone_changed(value: float) -> void:
|
||||
func _on_save_pressed() -> void:
|
||||
if loadout == null:
|
||||
return
|
||||
|
||||
# Local cache (offline fallback / fast reopen).
|
||||
var json := loadout.to_json()
|
||||
var file := FileAccess.open("user://loadout.json", FileAccess.WRITE)
|
||||
if file:
|
||||
file.store_string(json)
|
||||
file.close()
|
||||
_status_label.text = "Loadout saved."
|
||||
|
||||
# Apply to the live in-world avatar immediately.
|
||||
if _system != null:
|
||||
_system.set_loadout_from_dict(JSON.parse_string(json))
|
||||
|
||||
# Persist to the backend if we're signed in.
|
||||
var api := get_node_or_null("/root/ApiClient")
|
||||
if api != null and api.has_token():
|
||||
_status_label.text = "Saving…"
|
||||
var res: Dictionary = await api.save_avatar_loadout(JSON.parse_string(json))
|
||||
_status_label.text = "Saved." if res.get("ok", false) else "Saved locally (backend: %s)" % res.get("detail", "error")
|
||||
else:
|
||||
_status_label.text = "Failed to save loadout."
|
||||
_status_label.text = "Saved locally (not signed in)."
|
||||
|
||||
|
||||
func _on_load_pressed() -> void:
|
||||
|
||||
Reference in New Issue
Block a user