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",
|
"display_name": "Ponytail",
|
||||||
"mount_bone": "HeadTop"
|
"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": {
|
"face_glasses_01": {
|
||||||
"type": "attachment",
|
"type": "attachment",
|
||||||
"slot": "face",
|
"slot": "face",
|
||||||
@@ -71,6 +79,14 @@
|
|||||||
"display_name": "Left Glove",
|
"display_name": "Left Glove",
|
||||||
"mount_bone": "LeftHand"
|
"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": {
|
"right_hand_glove_01": {
|
||||||
"type": "attachment",
|
"type": "attachment",
|
||||||
"slot": "right_hand",
|
"slot": "right_hand",
|
||||||
@@ -79,6 +95,14 @@
|
|||||||
"display_name": "Right Glove",
|
"display_name": "Right Glove",
|
||||||
"mount_bone": "RightHand"
|
"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": {
|
"left_foot_boot_01": {
|
||||||
"type": "attachment",
|
"type": "attachment",
|
||||||
"slot": "left_foot",
|
"slot": "left_foot",
|
||||||
@@ -87,6 +111,14 @@
|
|||||||
"display_name": "Left Boot",
|
"display_name": "Left Boot",
|
||||||
"mount_bone": "LeftFoot"
|
"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": {
|
"right_foot_boot_01": {
|
||||||
"type": "attachment",
|
"type": "attachment",
|
||||||
"slot": "right_foot",
|
"slot": "right_foot",
|
||||||
@@ -95,6 +127,14 @@
|
|||||||
"display_name": "Right Boot",
|
"display_name": "Right Boot",
|
||||||
"mount_bone": "RightFoot"
|
"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": {
|
"accessory_belt_01": {
|
||||||
"type": "attachment",
|
"type": "attachment",
|
||||||
"slot": "accessory",
|
"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:
|
## Category tabs definition per Lead Architect blueprint:
|
||||||
## Tab 0: "Body & Head" (slots: base_body, head, hair, face)
|
## 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 2: "Accessories" (slots: back, accessory)
|
||||||
## Tab 3: "Skin & Colors" (skin tone presets, shading slider, custom RGB sliders)
|
## Tab 3: "Skin & Colors" (skin tone presets, shading slider, custom RGB sliders)
|
||||||
const CATEGORIES: Array[Dictionary] = [
|
const CATEGORIES: Array[Dictionary] = [
|
||||||
@@ -52,7 +52,7 @@ const CATEGORIES: Array[Dictionary] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Clothing",
|
"name": "Clothing",
|
||||||
"slots": ["chest", "pants", "torso", "left_foot", "left_hand"]
|
"slots": ["chest", "pants", "torso", "left_hand", "right_hand", "left_foot", "right_foot"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Accessories",
|
"name": "Accessories",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ var _friends_overlay: Control = null
|
|||||||
var _report_modal: Control = null
|
var _report_modal: Control = null
|
||||||
var _pause_menu: Control = null
|
var _pause_menu: Control = null
|
||||||
var _controls_modal: Control = null
|
var _controls_modal: Control = null
|
||||||
|
var _controls_pill: Control = null
|
||||||
var _hud_layer: CanvasLayer = null
|
var _hud_layer: CanvasLayer = null
|
||||||
var _overlay_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))
|
hint_lbl.add_theme_color_override("font_color", Color(0.85, 0.9, 0.95, 0.85))
|
||||||
controls_pill.add_child(hint_lbl)
|
controls_pill.add_child(hint_lbl)
|
||||||
_hud_layer.add_child(controls_pill)
|
_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 editing in creator test mode, show status badge
|
||||||
if Session.editing and Session.world_id != null and int(Session.world_id) > 0:
|
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.closed.connect(_close_build_mode)
|
||||||
_build_editor.exit_browser_requested.connect(_exit_editor_to_browser)
|
_build_editor.exit_browser_requested.connect(_exit_editor_to_browser)
|
||||||
add_child(_build_editor)
|
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
|
## 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):
|
if _build_editor != null and is_instance_valid(_build_editor):
|
||||||
_build_editor.queue_free()
|
_build_editor.queue_free()
|
||||||
_build_editor = null
|
_build_editor = null
|
||||||
|
if _controls_pill != null and is_instance_valid(_controls_pill):
|
||||||
|
_controls_pill.visible = true
|
||||||
for child in get_children():
|
for child in get_children():
|
||||||
if child is ChunkSaveService:
|
if child is ChunkSaveService:
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
@@ -760,6 +767,8 @@ func _open_editor_for_world() -> void:
|
|||||||
_build_editor.closed.connect(_close_build_mode)
|
_build_editor.closed.connect(_close_build_mode)
|
||||||
_build_editor.exit_browser_requested.connect(_exit_editor_to_browser)
|
_build_editor.exit_browser_requested.connect(_exit_editor_to_browser)
|
||||||
add_child(_build_editor)
|
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
|
## 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 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
|
var enabled: bool = audio_mgr.is_sound_enabled() if audio_mgr and audio_mgr.has_method("is_sound_enabled") else true
|
||||||
if enabled:
|
if enabled:
|
||||||
btn.text = "🔊 Sound: ON"
|
btn.text = "🔊 Sound: ON [M]"
|
||||||
|
btn.tooltip_text = "Sound is enabled. Click or press [M] to mute."
|
||||||
else:
|
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:
|
func _ready() -> void:
|
||||||
@@ -149,7 +151,7 @@ func _ready() -> void:
|
|||||||
var sound_btn := Button.new()
|
var sound_btn := Button.new()
|
||||||
sound_btn.name = "SoundButton"
|
sound_btn.name = "SoundButton"
|
||||||
sound_btn.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
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_top = 10
|
||||||
sound_btn.offset_right = -310
|
sound_btn.offset_right = -310
|
||||||
sound_btn.offset_bottom = 38
|
sound_btn.offset_bottom = 38
|
||||||
@@ -161,16 +163,6 @@ func _ready() -> void:
|
|||||||
audio_mgr.toggle_sound()
|
audio_mgr.toggle_sound()
|
||||||
_update_sound_button(sound_btn)
|
_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)
|
add_child(sound_btn)
|
||||||
|
|
||||||
# Cache node references.
|
# Cache node references.
|
||||||
@@ -178,6 +170,50 @@ func _ready() -> void:
|
|||||||
_login_button = $CenterContainer/VBoxContainer/AuthButtonsRow/LoginButton
|
_login_button = $CenterContainer/VBoxContainer/AuthButtonsRow/LoginButton
|
||||||
_register_button = $CenterContainer/VBoxContainer/AuthButtonsRow/RegisterButton
|
_register_button = $CenterContainer/VBoxContainer/AuthButtonsRow/RegisterButton
|
||||||
_auth_panel = $CenterContainer/VBoxContainer/AuthPanel
|
_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
|
_auth_mode_label = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/AuthModeLabel
|
||||||
_username_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/UsernameEdit
|
_username_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/UsernameEdit
|
||||||
_password_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/PasswordEdit
|
_password_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/PasswordEdit
|
||||||
@@ -512,6 +548,15 @@ func _clear_status() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> 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 event.is_action_pressed("ui_cancel"):
|
||||||
if has_node("ControlsModal"):
|
if has_node("ControlsModal"):
|
||||||
get_node("ControlsModal").queue_free()
|
get_node("ControlsModal").queue_free()
|
||||||
|
|||||||
@@ -84,14 +84,20 @@ func _run() -> void:
|
|||||||
# Check Category constants
|
# Check Category constants
|
||||||
_check("Category 0 has slots: base_body, head, hair, face",
|
_check("Category 0 has slots: base_body, head, hair, face",
|
||||||
ui.CATEGORIES[0]["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",
|
_check("Category 1 has slots: chest, pants, torso, left_hand, right_hand, left_foot, right_foot",
|
||||||
ui.CATEGORIES[1]["slots"] == ["chest", "pants", "torso", "left_foot", "left_hand"])
|
ui.CATEGORIES[1]["slots"] == ["chest", "pants", "torso", "left_hand", "right_hand", "left_foot", "right_foot"])
|
||||||
_check("Category 2 has slots: back, accessory",
|
_check("Category 2 has slots: back, accessory",
|
||||||
ui.CATEGORIES[2]["slots"] == ["back", "accessory"])
|
ui.CATEGORIES[2]["slots"] == ["back", "accessory"])
|
||||||
|
|
||||||
# 3. Friendly Item Names Verification
|
# 3. Friendly Item Names Verification
|
||||||
var mohawk_name: String = ui.get_friendly_item_name("hair_mohawk_01")
|
var mohawk_name: String = ui.get_friendly_item_name("hair_mohawk_01")
|
||||||
_check("hair_mohawk_01 friendly name is 'Mohawk'", mohawk_name == "Mohawk")
|
_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")
|
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")
|
_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")
|
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
|
ui._on_category_tab_changed(1) # Clothing
|
||||||
await process_frame
|
await process_frame
|
||||||
var slot_list: HBoxContainer = ui.find_child("SlotList", true, false) as HBoxContainer
|
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
|
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)
|
_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
|
# 6. Skin & Colors Tab Verification
|
||||||
ui._on_category_tab_changed(3) # Skin & Colors
|
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())
|
base_bodies.size() == 0 or not loadout.base_body.is_empty())
|
||||||
|
|
||||||
var hair_options: Array[String] = system.get_assets_for_slot("hair")
|
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:
|
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",
|
_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.
|
# 4. Loadout serializes and survives a round trip.
|
||||||
var json_str: String = loadout.to_json()
|
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)],
|
"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)],
|
"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)],
|
"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)],
|
"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)],
|
"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_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)],
|
"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]
|
var spec: Array = ATTACHMENTS[id]
|
||||||
_gen_attachment(id, spec[0], spec[1], spec[2])
|
_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
|
print("\n%s" % ("DONE — all placeholder avatar GLBs written" if _fail == 0
|
||||||
else "FAILED (%d errors)" % _fail))
|
else "FAILED (%d errors)" % _fail))
|
||||||
quit(_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
|
var m := BoxMesh.new(); m.size = Vector3(0.30, 0.24, 0.34); return m
|
||||||
"boot":
|
"boot":
|
||||||
var m := BoxMesh.new(); m.size = Vector3(0.34, 0.16, 0.42); return m
|
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":
|
"belt":
|
||||||
var m := TorusMesh.new(); m.inner_radius = 0.22; m.outer_radius = 0.30; return m
|
var m := TorusMesh.new(); m.inner_radius = 0.22; m.outer_radius = 0.30; return m
|
||||||
"necklace":
|
"necklace":
|
||||||
@@ -290,6 +300,109 @@ func _attachment_mesh(kind: String) -> Mesh:
|
|||||||
return BoxMesh.new()
|
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
|
# GLB export
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user