feat: Add right hand/foot editor options, Flintstones Water Buffalo Lodge hat, hide controls in world editor, and easy main menu sound toggle
checkpoint / checkpoint (push) Successful in 6m19s
checkpoint / checkpoint (push) Successful in 6m19s
This commit is contained in:
@@ -31,6 +31,14 @@
|
||||
"display_name": "Ponytail",
|
||||
"mount_bone": "HeadTop"
|
||||
},
|
||||
"hair_waterbuffalo_01": {
|
||||
"type": "attachment",
|
||||
"slot": "hair",
|
||||
"path": "res://assets/avatars/hair_waterbuffalo_01.glb",
|
||||
"version": "1.0.0",
|
||||
"display_name": "Water Buffalo Lodge Hat",
|
||||
"mount_bone": "HeadTop"
|
||||
},
|
||||
"face_glasses_01": {
|
||||
"type": "attachment",
|
||||
"slot": "face",
|
||||
@@ -71,6 +79,14 @@
|
||||
"display_name": "Left Glove",
|
||||
"mount_bone": "LeftHand"
|
||||
},
|
||||
"left_hand_wristband_01": {
|
||||
"type": "attachment",
|
||||
"slot": "left_hand",
|
||||
"path": "res://assets/avatars/left_hand_wristband_01.glb",
|
||||
"version": "1.0.0",
|
||||
"display_name": "Left Gold Band",
|
||||
"mount_bone": "LeftHand"
|
||||
},
|
||||
"right_hand_glove_01": {
|
||||
"type": "attachment",
|
||||
"slot": "right_hand",
|
||||
@@ -79,6 +95,14 @@
|
||||
"display_name": "Right Glove",
|
||||
"mount_bone": "RightHand"
|
||||
},
|
||||
"right_hand_wristband_01": {
|
||||
"type": "attachment",
|
||||
"slot": "right_hand",
|
||||
"path": "res://assets/avatars/right_hand_wristband_01.glb",
|
||||
"version": "1.0.0",
|
||||
"display_name": "Right Gold Band",
|
||||
"mount_bone": "RightHand"
|
||||
},
|
||||
"left_foot_boot_01": {
|
||||
"type": "attachment",
|
||||
"slot": "left_foot",
|
||||
@@ -87,6 +111,14 @@
|
||||
"display_name": "Left Boot",
|
||||
"mount_bone": "LeftFoot"
|
||||
},
|
||||
"left_foot_sneaker_01": {
|
||||
"type": "attachment",
|
||||
"slot": "left_foot",
|
||||
"path": "res://assets/avatars/left_foot_sneaker_01.glb",
|
||||
"version": "1.0.0",
|
||||
"display_name": "Left Sneaker",
|
||||
"mount_bone": "LeftFoot"
|
||||
},
|
||||
"right_foot_boot_01": {
|
||||
"type": "attachment",
|
||||
"slot": "right_foot",
|
||||
@@ -95,6 +127,14 @@
|
||||
"display_name": "Right Boot",
|
||||
"mount_bone": "RightFoot"
|
||||
},
|
||||
"right_foot_sneaker_01": {
|
||||
"type": "attachment",
|
||||
"slot": "right_foot",
|
||||
"path": "res://assets/avatars/right_foot_sneaker_01.glb",
|
||||
"version": "1.0.0",
|
||||
"display_name": "Right Sneaker",
|
||||
"mount_bone": "RightFoot"
|
||||
},
|
||||
"accessory_belt_01": {
|
||||
"type": "attachment",
|
||||
"slot": "accessory",
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://dbpg1j858253k"
|
||||
path="res://.godot/imported/hair_waterbuffalo_01.glb-c029a4cc5c7f4da664b2a6ff40a4d5a5.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/avatars/hair_waterbuffalo_01.glb"
|
||||
dest_files=["res://.godot/imported/hair_waterbuffalo_01.glb-c029a4cc5c7f4da664b2a6ff40a4d5a5.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://cdu5ncc6txi2a"
|
||||
path="res://.godot/imported/left_foot_sneaker_01.glb-58178eceb7610b00fcdd19ec562d32ee.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/avatars/left_foot_sneaker_01.glb"
|
||||
dest_files=["res://.godot/imported/left_foot_sneaker_01.glb-58178eceb7610b00fcdd19ec562d32ee.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://bd2pj86s3jgw6"
|
||||
path="res://.godot/imported/left_hand_wristband_01.glb-bbb8beaefb98cfa41b275a9c621f1f25.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/avatars/left_hand_wristband_01.glb"
|
||||
dest_files=["res://.godot/imported/left_hand_wristband_01.glb-bbb8beaefb98cfa41b275a9c621f1f25.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://da2jib466th8w"
|
||||
path="res://.godot/imported/right_foot_sneaker_01.glb-54204fa9ac3cba3033c3591ef48c54c7.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/avatars/right_foot_sneaker_01.glb"
|
||||
dest_files=["res://.godot/imported/right_foot_sneaker_01.glb-54204fa9ac3cba3033c3591ef48c54c7.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://b64ejiiluvql4"
|
||||
path="res://.godot/imported/right_hand_wristband_01.glb-65f50dbbe9aaf5f2de1555fec5ca3385.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/avatars/right_hand_wristband_01.glb"
|
||||
dest_files=["res://.godot/imported/right_hand_wristband_01.glb-65f50dbbe9aaf5f2de1555fec5ca3385.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
@@ -42,7 +42,7 @@ signal loadout_changed(loadout: AvatarLoadout)
|
||||
|
||||
## Category tabs definition per Lead Architect blueprint:
|
||||
## Tab 0: "Body & Head" (slots: base_body, head, hair, face)
|
||||
## Tab 1: "Clothing" (slots: chest, pants, torso, left_foot, left_hand)
|
||||
## Tab 1: "Clothing" (slots: chest, pants, torso, left_hand, right_hand, left_foot, right_foot)
|
||||
## Tab 2: "Accessories" (slots: back, accessory)
|
||||
## Tab 3: "Skin & Colors" (skin tone presets, shading slider, custom RGB sliders)
|
||||
const CATEGORIES: Array[Dictionary] = [
|
||||
@@ -52,7 +52,7 @@ const CATEGORIES: Array[Dictionary] = [
|
||||
},
|
||||
{
|
||||
"name": "Clothing",
|
||||
"slots": ["chest", "pants", "torso", "left_foot", "left_hand"]
|
||||
"slots": ["chest", "pants", "torso", "left_hand", "right_hand", "left_foot", "right_foot"]
|
||||
},
|
||||
{
|
||||
"name": "Accessories",
|
||||
|
||||
@@ -45,6 +45,7 @@ var _friends_overlay: Control = null
|
||||
var _report_modal: Control = null
|
||||
var _pause_menu: Control = null
|
||||
var _controls_modal: Control = null
|
||||
var _controls_pill: Control = null
|
||||
var _hud_layer: CanvasLayer = null
|
||||
var _overlay_layer: CanvasLayer = null
|
||||
|
||||
@@ -335,6 +336,8 @@ func _setup_in_game_hud() -> void:
|
||||
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)
|
||||
_controls_pill = controls_pill
|
||||
_controls_pill.visible = (_build_editor == null or not is_instance_valid(_build_editor))
|
||||
|
||||
# If editing in creator test mode, show status badge
|
||||
if Session.editing and Session.world_id != null and int(Session.world_id) > 0:
|
||||
@@ -687,6 +690,8 @@ func _open_build_mode() -> void:
|
||||
_build_editor.closed.connect(_close_build_mode)
|
||||
_build_editor.exit_browser_requested.connect(_exit_editor_to_browser)
|
||||
add_child(_build_editor)
|
||||
if _controls_pill != null and is_instance_valid(_controls_pill):
|
||||
_controls_pill.visible = false
|
||||
|
||||
|
||||
## Shared teardown for both close paths (F2 again, or the editor's own
|
||||
@@ -697,6 +702,8 @@ func _close_build_mode() -> void:
|
||||
if _build_editor != null and is_instance_valid(_build_editor):
|
||||
_build_editor.queue_free()
|
||||
_build_editor = null
|
||||
if _controls_pill != null and is_instance_valid(_controls_pill):
|
||||
_controls_pill.visible = true
|
||||
for child in get_children():
|
||||
if child is ChunkSaveService:
|
||||
child.queue_free()
|
||||
@@ -760,6 +767,8 @@ func _open_editor_for_world() -> void:
|
||||
_build_editor.closed.connect(_close_build_mode)
|
||||
_build_editor.exit_browser_requested.connect(_exit_editor_to_browser)
|
||||
add_child(_build_editor)
|
||||
if _controls_pill != null and is_instance_valid(_controls_pill):
|
||||
_controls_pill.visible = false
|
||||
|
||||
|
||||
## Close path for the non-joined editor (ESC, or the editor's own Close
|
||||
|
||||
+58
-13
@@ -94,9 +94,11 @@ static func _update_sound_button(btn: Button) -> void:
|
||||
var audio_mgr: Node = main_loop.root.get_node_or_null("AudioManager") if main_loop is SceneTree else null
|
||||
var enabled: bool = audio_mgr.is_sound_enabled() if audio_mgr and audio_mgr.has_method("is_sound_enabled") else true
|
||||
if enabled:
|
||||
btn.text = "🔊 Sound: ON"
|
||||
btn.text = "🔊 Sound: ON [M]"
|
||||
btn.tooltip_text = "Sound is enabled. Click or press [M] to mute."
|
||||
else:
|
||||
btn.text = "🔇 Sound: OFF"
|
||||
btn.text = "🔇 Sound: OFF [M]"
|
||||
btn.tooltip_text = "Sound is muted. Click or press [M] to unmute."
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -149,7 +151,7 @@ func _ready() -> void:
|
||||
var sound_btn := Button.new()
|
||||
sound_btn.name = "SoundButton"
|
||||
sound_btn.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||||
sound_btn.offset_left = -440
|
||||
sound_btn.offset_left = -460
|
||||
sound_btn.offset_top = 10
|
||||
sound_btn.offset_right = -310
|
||||
sound_btn.offset_bottom = 38
|
||||
@@ -161,16 +163,6 @@ func _ready() -> void:
|
||||
audio_mgr.toggle_sound()
|
||||
_update_sound_button(sound_btn)
|
||||
)
|
||||
var audio_singleton = get_node_or_null("/root/AudioManager")
|
||||
if audio_singleton and audio_singleton.has_signal("sound_toggled"):
|
||||
var on_toggled = func(_enabled: bool):
|
||||
if is_instance_valid(sound_btn):
|
||||
_update_sound_button(sound_btn)
|
||||
audio_singleton.sound_toggled.connect(on_toggled)
|
||||
sound_btn.tree_exiting.connect(func():
|
||||
if is_instance_valid(audio_singleton) and audio_singleton.sound_toggled.is_connected(on_toggled):
|
||||
audio_singleton.sound_toggled.disconnect(on_toggled)
|
||||
)
|
||||
add_child(sound_btn)
|
||||
|
||||
# Cache node references.
|
||||
@@ -178,6 +170,50 @@ func _ready() -> void:
|
||||
_login_button = $CenterContainer/VBoxContainer/AuthButtonsRow/LoginButton
|
||||
_register_button = $CenterContainer/VBoxContainer/AuthButtonsRow/RegisterButton
|
||||
_auth_panel = $CenterContainer/VBoxContainer/AuthPanel
|
||||
|
||||
# Prominent sound button in AuthButtonsRow on the logged-out start screen
|
||||
var auth_sound_btn := Button.new()
|
||||
auth_sound_btn.name = "AuthSoundButton"
|
||||
auth_sound_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
auth_sound_btn.add_theme_font_size_override("font_size", 13)
|
||||
_update_sound_button(auth_sound_btn)
|
||||
auth_sound_btn.pressed.connect(func():
|
||||
var audio_mgr = get_node_or_null("/root/AudioManager")
|
||||
if audio_mgr and audio_mgr.has_method("toggle_sound"):
|
||||
audio_mgr.toggle_sound()
|
||||
_update_sound_button(auth_sound_btn)
|
||||
)
|
||||
_auth_buttons_row.add_child(auth_sound_btn)
|
||||
|
||||
# Sound toggle also inside AuthPanel so user can toggle sound without closing the auth form
|
||||
var panel_sound_btn := Button.new()
|
||||
panel_sound_btn.name = "PanelSoundButton"
|
||||
panel_sound_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
panel_sound_btn.add_theme_font_size_override("font_size", 12)
|
||||
_update_sound_button(panel_sound_btn)
|
||||
panel_sound_btn.pressed.connect(func():
|
||||
var audio_mgr = get_node_or_null("/root/AudioManager")
|
||||
if audio_mgr and audio_mgr.has_method("toggle_sound"):
|
||||
audio_mgr.toggle_sound()
|
||||
_update_sound_button(panel_sound_btn)
|
||||
)
|
||||
var auth_vbox = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox
|
||||
auth_vbox.add_child(panel_sound_btn)
|
||||
|
||||
var audio_singleton = get_node_or_null("/root/AudioManager")
|
||||
if audio_singleton and audio_singleton.has_signal("sound_toggled"):
|
||||
var on_toggled = func(_enabled: bool):
|
||||
if is_instance_valid(sound_btn):
|
||||
_update_sound_button(sound_btn)
|
||||
if is_instance_valid(auth_sound_btn):
|
||||
_update_sound_button(auth_sound_btn)
|
||||
if is_instance_valid(panel_sound_btn):
|
||||
_update_sound_button(panel_sound_btn)
|
||||
audio_singleton.sound_toggled.connect(on_toggled)
|
||||
tree_exiting.connect(func():
|
||||
if is_instance_valid(audio_singleton) and audio_singleton.sound_toggled.is_connected(on_toggled):
|
||||
audio_singleton.sound_toggled.disconnect(on_toggled)
|
||||
)
|
||||
_auth_mode_label = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/AuthModeLabel
|
||||
_username_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/UsernameEdit
|
||||
_password_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/PasswordEdit
|
||||
@@ -512,6 +548,15 @@ func _clear_status() -> void:
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
if event.keycode == KEY_M:
|
||||
var focus_owner := get_viewport().gui_get_focus_owner()
|
||||
if not (focus_owner is LineEdit or focus_owner is TextEdit):
|
||||
var audio_mgr = get_node_or_null("/root/AudioManager")
|
||||
if audio_mgr and audio_mgr.has_method("toggle_sound"):
|
||||
audio_mgr.toggle_sound()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if event.is_action_pressed("ui_cancel"):
|
||||
if has_node("ControlsModal"):
|
||||
get_node("ControlsModal").queue_free()
|
||||
|
||||
@@ -84,14 +84,20 @@ func _run() -> void:
|
||||
# Check Category constants
|
||||
_check("Category 0 has slots: base_body, head, hair, face",
|
||||
ui.CATEGORIES[0]["slots"] == ["base_body", "head", "hair", "face"])
|
||||
_check("Category 1 has slots: chest, pants, torso, left_foot, left_hand",
|
||||
ui.CATEGORIES[1]["slots"] == ["chest", "pants", "torso", "left_foot", "left_hand"])
|
||||
_check("Category 1 has slots: chest, pants, torso, left_hand, right_hand, left_foot, right_foot",
|
||||
ui.CATEGORIES[1]["slots"] == ["chest", "pants", "torso", "left_hand", "right_hand", "left_foot", "right_foot"])
|
||||
_check("Category 2 has slots: back, accessory",
|
||||
ui.CATEGORIES[2]["slots"] == ["back", "accessory"])
|
||||
|
||||
# 3. Friendly Item Names Verification
|
||||
var mohawk_name: String = ui.get_friendly_item_name("hair_mohawk_01")
|
||||
_check("hair_mohawk_01 friendly name is 'Mohawk'", mohawk_name == "Mohawk")
|
||||
var waterbuffalo_name: String = ui.get_friendly_item_name("hair_waterbuffalo_01")
|
||||
_check("hair_waterbuffalo_01 friendly name is 'Water Buffalo Lodge Hat'", waterbuffalo_name == "Water Buffalo Lodge Hat")
|
||||
var right_glove_name: String = ui.get_friendly_item_name("right_hand_glove_01")
|
||||
_check("right_hand_glove_01 friendly name is 'Right Glove'", right_glove_name == "Right Glove")
|
||||
var right_boot_name: String = ui.get_friendly_item_name("right_foot_boot_01")
|
||||
_check("right_foot_boot_01 friendly name is 'Right Boot'", right_boot_name == "Right Boot")
|
||||
var cyber_name: String = ui.get_friendly_item_name("head_cyber_01")
|
||||
_check("head_cyber_01 friendly name is 'Cyber Head'", cyber_name == "Cyber Head")
|
||||
var cargo_name: String = ui.get_friendly_item_name("pants_cargo_01")
|
||||
@@ -152,9 +158,19 @@ func _run() -> void:
|
||||
ui._on_category_tab_changed(1) # Clothing
|
||||
await process_frame
|
||||
var slot_list: HBoxContainer = ui.find_child("SlotList", true, false) as HBoxContainer
|
||||
_check("Switching to Clothing tab populates slot buttons", slot_list != null and slot_list.get_child_count() >= 3)
|
||||
_check("Switching to Clothing tab populates slot buttons", slot_list != null and slot_list.get_child_count() >= 5)
|
||||
var chest_slot_btn: Button = ui.find_child("Slot_chest", true, false) as Button
|
||||
_check("Slot_chest button exists in Clothing tab", chest_slot_btn != null)
|
||||
var right_hand_slot_btn: Button = ui.find_child("Slot_right_hand", true, false) as Button
|
||||
_check("Slot_right_hand button exists in Clothing tab", right_hand_slot_btn != null)
|
||||
var right_foot_slot_btn: Button = ui.find_child("Slot_right_foot", true, false) as Button
|
||||
_check("Slot_right_foot button exists in Clothing tab", right_foot_slot_btn != null)
|
||||
|
||||
# Verify selecting right_hand slot populates items
|
||||
ui._select_slot("right_hand")
|
||||
await process_frame
|
||||
var right_glove_btn: Button = ui.find_child("Item_right_hand_glove_01", true, false) as Button
|
||||
_check("Item_right_hand_glove_01 button exists under right_hand slot", right_glove_btn != null)
|
||||
|
||||
# 6. Skin & Colors Tab Verification
|
||||
ui._on_category_tab_changed(3) # Skin & Colors
|
||||
|
||||
@@ -52,10 +52,11 @@ func _run() -> void:
|
||||
base_bodies.size() == 0 or not loadout.base_body.is_empty())
|
||||
|
||||
var hair_options: Array[String] = system.get_assets_for_slot("hair")
|
||||
_check("hair options include hair_waterbuffalo_01", hair_options.has("hair_waterbuffalo_01"))
|
||||
if hair_options.size() > 0:
|
||||
loadout.set_equipment("hair", hair_options[0])
|
||||
loadout.set_equipment("hair", "hair_waterbuffalo_01" if hair_options.has("hair_waterbuffalo_01") else hair_options[0])
|
||||
_check("equipping a real catalog hair item succeeds",
|
||||
loadout.slots.get("hair") == hair_options[0])
|
||||
loadout.slots.get("hair") == ("hair_waterbuffalo_01" if hair_options.has("hair_waterbuffalo_01") else hair_options[0]))
|
||||
|
||||
# 4. Loadout serializes and survives a round trip.
|
||||
var json_str: String = loadout.to_json()
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
extends SceneTree
|
||||
## Smoke test verifying:
|
||||
## 1. Character editor has right_hand and right_foot options.
|
||||
## 2. Water Buffalo Lodge Hat (hair_waterbuffalo_01) exists, resolves, and loads.
|
||||
## 3. In world editor / build mode, controls_pill is hidden.
|
||||
## 4. Main menu sound toggle is accessible when logged out and responds to 'M' key.
|
||||
|
||||
var passed := 0
|
||||
var failed := 0
|
||||
|
||||
func _initialize() -> void:
|
||||
_run()
|
||||
|
||||
func _check(desc: String, cond: bool) -> bool:
|
||||
if cond:
|
||||
print(" PASS: ", desc)
|
||||
passed += 1
|
||||
else:
|
||||
printerr(" FAIL: ", desc)
|
||||
failed += 1
|
||||
return cond
|
||||
|
||||
func _run() -> void:
|
||||
await process_frame
|
||||
print("SMOKE: Starting Editor Controls, Water Buffalo Hat & Sound verification...")
|
||||
|
||||
# --- 1. Water Buffalo Hat & Character Editor Slots ---
|
||||
var AvatarCatalogScript = preload("res://scripts/avatar/avatar_catalog.gd")
|
||||
var catalog = AvatarCatalogScript.new()
|
||||
var cat_err = catalog.load_catalog()
|
||||
_check("Catalog loads successfully", cat_err == OK)
|
||||
|
||||
var hat = catalog.resolve("hair_waterbuffalo_01")
|
||||
_check("hair_waterbuffalo_01 exists in catalog", not hat.is_empty())
|
||||
_check("hair_waterbuffalo_01 slot is 'hair'", hat.get("slot", "") == "hair")
|
||||
_check("hair_waterbuffalo_01 mount_bone is 'HeadTop'", hat.get("mount_bone", "") == "HeadTop")
|
||||
_check("hair_waterbuffalo_01 display_name is 'Water Buffalo Lodge Hat'", hat.get("display_name", "") == "Water Buffalo Lodge Hat")
|
||||
|
||||
var AvatarCustomizationUIScript = preload("res://scripts/avatar/avatar_customization_ui.gd")
|
||||
var ui = AvatarCustomizationUIScript.new()
|
||||
root.add_child(ui)
|
||||
await process_frame
|
||||
await process_frame
|
||||
|
||||
var clothing_slots: Array = ui.CATEGORIES[1]["slots"]
|
||||
_check("Clothing category contains 'right_hand'", clothing_slots.has("right_hand"))
|
||||
_check("Clothing category contains 'right_foot'", clothing_slots.has("right_foot"))
|
||||
|
||||
ui._select_slot("right_hand")
|
||||
await process_frame
|
||||
var right_hand_btn = ui.find_child("Item_right_hand_glove_01", true, false)
|
||||
_check("Item_right_hand_glove_01 exists in ItemList", right_hand_btn != null)
|
||||
|
||||
ui._select_slot("right_foot")
|
||||
await process_frame
|
||||
var right_foot_btn = ui.find_child("Item_right_foot_boot_01", true, false)
|
||||
_check("Item_right_foot_boot_01 exists in ItemList", right_foot_btn != null)
|
||||
|
||||
ui._select_slot("hair")
|
||||
await process_frame
|
||||
var hat_btn = ui.find_child("Item_hair_waterbuffalo_01", true, false)
|
||||
_check("Item_hair_waterbuffalo_01 exists in ItemList", hat_btn != null)
|
||||
|
||||
# Equip water buffalo hat
|
||||
ui._on_equip_pressed("hair_waterbuffalo_01")
|
||||
await process_frame
|
||||
_check("Water Buffalo Hat equipped in loadout", ui.loadout.get_equipment("hair") == "hair_waterbuffalo_01")
|
||||
|
||||
ui.queue_free()
|
||||
await process_frame
|
||||
|
||||
# --- 2. Main Menu Sound Controls & Keyboard 'M' ---
|
||||
var audio_mgr = root.get_node_or_null("AudioManager")
|
||||
_check("AudioManager autoload exists", audio_mgr != null)
|
||||
if audio_mgr:
|
||||
audio_mgr.set_sound_enabled(true, false)
|
||||
|
||||
var main_menu_scene = load("res://scenes/main_menu.tscn")
|
||||
var menu = main_menu_scene.instantiate()
|
||||
root.add_child(menu)
|
||||
await process_frame
|
||||
|
||||
var sound_btn = menu.find_child("SoundButton", true, false) as Button
|
||||
_check("SoundButton exists on MainMenu", sound_btn != null)
|
||||
var auth_sound_btn = menu.find_child("AuthSoundButton", true, false) as Button
|
||||
_check("AuthSoundButton exists in AuthButtonsRow", auth_sound_btn != null)
|
||||
var panel_sound_btn = menu.find_child("PanelSoundButton", true, false) as Button
|
||||
_check("PanelSoundButton exists in AuthPanel", panel_sound_btn != null)
|
||||
|
||||
_check("SoundButton text contains 'Sound: ON'", sound_btn != null and sound_btn.text.contains("Sound: ON"))
|
||||
_check("AuthSoundButton text contains 'Sound: ON'", auth_sound_btn != null and auth_sound_btn.text.contains("Sound: ON"))
|
||||
|
||||
# Test 'M' key shortcut
|
||||
var key_ev = InputEventKey.new()
|
||||
key_ev.pressed = true
|
||||
key_ev.keycode = KEY_M
|
||||
menu._unhandled_input(key_ev)
|
||||
await process_frame
|
||||
|
||||
_check("Pressing 'M' toggles sound OFF", audio_mgr != null and not audio_mgr.is_sound_enabled())
|
||||
_check("SoundButton text updated to 'Sound: OFF'", sound_btn != null and sound_btn.text.contains("Sound: OFF"))
|
||||
_check("AuthSoundButton text updated to 'Sound: OFF'", auth_sound_btn != null and auth_sound_btn.text.contains("Sound: OFF"))
|
||||
|
||||
# Toggle back ON with AuthSoundButton
|
||||
if auth_sound_btn != null:
|
||||
auth_sound_btn.emit_signal("pressed")
|
||||
await process_frame
|
||||
_check("AuthSoundButton click toggles sound ON", audio_mgr != null and audio_mgr.is_sound_enabled())
|
||||
_check("SoundButton text restored to 'Sound: ON'", sound_btn != null and sound_btn.text.contains("Sound: ON"))
|
||||
|
||||
menu.queue_free()
|
||||
await process_frame
|
||||
|
||||
# --- 3. World Editor Controls Banner Hiding ---
|
||||
var game_scene = load("res://scenes/game.tscn")
|
||||
_check("game.tscn loads successfully", game_scene != null)
|
||||
if game_scene == null:
|
||||
_finish()
|
||||
return
|
||||
var game = game_scene.instantiate()
|
||||
root.add_child(game)
|
||||
await process_frame
|
||||
|
||||
var pill = game.find_child("ControlsPill", true, false) as Control
|
||||
_check("ControlsPill exists in game HUD", pill != null)
|
||||
_check("ControlsPill starts visible during regular play", pill != null and pill.visible)
|
||||
|
||||
# Open build mode
|
||||
game._open_build_mode()
|
||||
await process_frame
|
||||
_check("ControlsPill is HIDDEN when in build mode", pill != null and not pill.visible)
|
||||
|
||||
# Close build mode
|
||||
game._close_build_mode()
|
||||
await process_frame
|
||||
_check("ControlsPill is RESTORED when exiting build mode", pill != null and pill.visible)
|
||||
|
||||
game.queue_free()
|
||||
await process_frame
|
||||
|
||||
print("\n=== SMOKE RESULT: %d/%d passed ===" % [passed, passed + failed])
|
||||
quit(0 if failed == 0 else 1)
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
print("\n=== SMOKE RESULT: %d/%d passed ===" % [passed, passed + failed])
|
||||
quit(0 if failed == 0 else 1)
|
||||
@@ -96,8 +96,12 @@ const ATTACHMENTS := {
|
||||
"pants_cargo_01": ["hips", Vector3(0, -0.04, 0.0), Color(0.30, 0.35, 0.45)],
|
||||
"left_hand_glove_01": ["blob", Vector3(0, 0.0, 0.0), Color(0.15, 0.15, 0.18)],
|
||||
"right_hand_glove_01": ["blob", Vector3(0, 0.0, 0.0), Color(0.15, 0.15, 0.18)],
|
||||
"left_hand_wristband_01": ["band", Vector3(0, 0.02, 0.0), Color(0.95, 0.80, 0.15)],
|
||||
"right_hand_wristband_01": ["band", Vector3(0, 0.02, 0.0), Color(0.95, 0.80, 0.15)],
|
||||
"left_foot_boot_01": ["boot", Vector3(0, -0.02, -0.05), Color(0.12, 0.10, 0.09)],
|
||||
"right_foot_boot_01": ["boot", Vector3(0, -0.02, -0.05), Color(0.12, 0.10, 0.09)],
|
||||
"left_foot_sneaker_01": ["sneaker", Vector3(0, -0.02, -0.05), Color(0.85, 0.25, 0.25)],
|
||||
"right_foot_sneaker_01": ["sneaker", Vector3(0, -0.02, -0.05), Color(0.85, 0.25, 0.25)],
|
||||
"accessory_belt_01": ["belt", Vector3(0, 0.74, 0.0), Color(0.20, 0.18, 0.12)],
|
||||
"accessory_necklace_01": ["necklace", Vector3(0, 1.34, -0.10), Color(0.85, 0.75, 0.20)],
|
||||
}
|
||||
@@ -114,6 +118,8 @@ func _initialize() -> void:
|
||||
var spec: Array = ATTACHMENTS[id]
|
||||
_gen_attachment(id, spec[0], spec[1], spec[2])
|
||||
|
||||
_gen_water_buffalo_hat()
|
||||
|
||||
print("\n%s" % ("DONE — all placeholder avatar GLBs written" if _fail == 0
|
||||
else "FAILED (%d errors)" % _fail))
|
||||
quit(_fail)
|
||||
@@ -282,6 +288,10 @@ func _attachment_mesh(kind: String) -> Mesh:
|
||||
var m := BoxMesh.new(); m.size = Vector3(0.30, 0.24, 0.34); return m
|
||||
"boot":
|
||||
var m := BoxMesh.new(); m.size = Vector3(0.34, 0.16, 0.42); return m
|
||||
"sneaker":
|
||||
var m := BoxMesh.new(); m.size = Vector3(0.32, 0.14, 0.40); return m
|
||||
"band":
|
||||
var m := TorusMesh.new(); m.inner_radius = 0.10; m.outer_radius = 0.16; return m
|
||||
"belt":
|
||||
var m := TorusMesh.new(); m.inner_radius = 0.22; m.outer_radius = 0.30; return m
|
||||
"necklace":
|
||||
@@ -290,6 +300,109 @@ func _attachment_mesh(kind: String) -> Mesh:
|
||||
return BoxMesh.new()
|
||||
|
||||
|
||||
func _gen_water_buffalo_hat() -> void:
|
||||
var root := Node3D.new()
|
||||
root.name = "AttachmentRoot"
|
||||
|
||||
# 1. Hat Cap (royal blue felt lodge hat, sitting on head crown)
|
||||
var cap := MeshInstance3D.new()
|
||||
cap.name = "HatCap"
|
||||
var cap_mesh := CylinderMesh.new()
|
||||
cap_mesh.top_radius = 0.28
|
||||
cap_mesh.bottom_radius = 0.32
|
||||
cap_mesh.height = 0.26
|
||||
cap.mesh = cap_mesh
|
||||
cap.position = Vector3(0.0, 0.12, 0.0)
|
||||
var cap_mat := StandardMaterial3D.new()
|
||||
cap_mat.albedo_color = Color(0.13, 0.20, 0.58) # Flintstones Water Buffalo blue
|
||||
cap_mat.roughness = 0.95
|
||||
cap.material_override = cap_mat
|
||||
root.add_child(cap)
|
||||
|
||||
# 2. Fur Brim / Rim at bottom edge of cap
|
||||
var brim := MeshInstance3D.new()
|
||||
brim.name = "FurBrim"
|
||||
var brim_mesh := TorusMesh.new()
|
||||
brim_mesh.inner_radius = 0.28
|
||||
brim_mesh.outer_radius = 0.36
|
||||
brim.mesh = brim_mesh
|
||||
brim.position = Vector3(0.0, 0.01, 0.0)
|
||||
var brim_mat := StandardMaterial3D.new()
|
||||
brim_mat.albedo_color = Color(0.24, 0.18, 0.13) # Fur trim
|
||||
brim_mat.roughness = 1.0
|
||||
brim.material_override = brim_mat
|
||||
root.add_child(brim)
|
||||
|
||||
# 3. Lodge Gold Medallion on front (-Z is forward)
|
||||
var medal := MeshInstance3D.new()
|
||||
medal.name = "LodgeBadge"
|
||||
var medal_mesh := BoxMesh.new()
|
||||
medal_mesh.size = Vector3(0.12, 0.10, 0.04)
|
||||
medal.mesh = medal_mesh
|
||||
medal.position = Vector3(0.0, 0.12, -0.32)
|
||||
var medal_mat := StandardMaterial3D.new()
|
||||
medal_mat.albedo_color = Color(0.96, 0.82, 0.18) # Gold lodge crest
|
||||
medal_mat.metallic = 0.7
|
||||
medal_mat.roughness = 0.3
|
||||
medal.material_override = medal_mat
|
||||
root.add_child(medal)
|
||||
|
||||
# Horn material: ivory/bone
|
||||
var horn_mat := StandardMaterial3D.new()
|
||||
horn_mat.albedo_color = Color(0.92, 0.89, 0.82) # Bone/ivory
|
||||
horn_mat.roughness = 0.5
|
||||
|
||||
var tip_mat := StandardMaterial3D.new()
|
||||
tip_mat.albedo_color = Color(0.20, 0.17, 0.14) # Dark horn tip
|
||||
tip_mat.roughness = 0.6
|
||||
|
||||
# 4. Sweeping Curved Water Buffalo Horns (curving outward and up)
|
||||
for side in [-1.0, 1.0]:
|
||||
var side_name := "Right" if side < 0.0 else "Left"
|
||||
|
||||
# Base segment (projecting horizontally outwards from side of hat)
|
||||
var h_base := MeshInstance3D.new()
|
||||
h_base.name = "Horn_%s_Base" % side_name
|
||||
var base_mesh := CylinderMesh.new()
|
||||
base_mesh.bottom_radius = 0.08
|
||||
base_mesh.top_radius = 0.065
|
||||
base_mesh.height = 0.20
|
||||
h_base.mesh = base_mesh
|
||||
h_base.position = Vector3(side * 0.36, 0.12, 0.02)
|
||||
h_base.rotation_degrees = Vector3(10.0, side * -15.0, side * -70.0)
|
||||
h_base.material_override = horn_mat
|
||||
root.add_child(h_base)
|
||||
|
||||
# Mid segment (sweeping upward and outward)
|
||||
var h_mid := MeshInstance3D.new()
|
||||
h_mid.name = "Horn_%s_Mid" % side_name
|
||||
var mid_mesh := CylinderMesh.new()
|
||||
mid_mesh.bottom_radius = 0.065
|
||||
mid_mesh.top_radius = 0.045
|
||||
mid_mesh.height = 0.22
|
||||
h_mid.mesh = mid_mesh
|
||||
h_mid.position = Vector3(side * 0.52, 0.24, 0.0)
|
||||
h_mid.rotation_degrees = Vector3(15.0, side * -10.0, side * -25.0)
|
||||
h_mid.material_override = horn_mat
|
||||
root.add_child(h_mid)
|
||||
|
||||
# Tip segment (curving upward and inward to sharp tip)
|
||||
var h_tip := MeshInstance3D.new()
|
||||
h_tip.name = "Horn_%s_Tip" % side_name
|
||||
var tip_mesh := CylinderMesh.new()
|
||||
tip_mesh.bottom_radius = 0.045
|
||||
tip_mesh.top_radius = 0.005
|
||||
tip_mesh.height = 0.18
|
||||
h_tip.mesh = tip_mesh
|
||||
h_tip.position = Vector3(side * 0.56, 0.40, -0.04)
|
||||
h_tip.rotation_degrees = Vector3(20.0, 0.0, side * 20.0)
|
||||
h_tip.material_override = tip_mat
|
||||
root.add_child(h_tip)
|
||||
|
||||
_write_glb(root, "hair_waterbuffalo_01")
|
||||
root.free()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GLB export
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user