Load authored pattern masks by body component, add custom fur colors and the themed picker UI, and advance the signed profile protocol to version 6.
404 lines
13 KiB
GDScript
404 lines
13 KiB
GDScript
extends SceneTree
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
CharacterCustomizationCatalog.refresh_fur_pattern_assets()
|
|
var legacy_snapshot := CharacterCustomizationCatalog.default_snapshot()
|
|
legacy_snapshot[CharacterCustomizationCatalog.FUR_STYLE_ID] = "spots_bengal"
|
|
for added_id: String in [
|
|
CharacterCustomizationCatalog.FUR_STYLE_ARMS_ID,
|
|
CharacterCustomizationCatalog.FUR_STYLE_HEAD_ID,
|
|
CharacterCustomizationCatalog.FUR_STYLE_EARS_ID,
|
|
CharacterCustomizationCatalog.FUR_STYLE_TAIL_ID,
|
|
]:
|
|
legacy_snapshot.erase(added_id)
|
|
var migrated := CharacterCustomizationCatalog.sanitized_snapshot(
|
|
legacy_snapshot
|
|
)
|
|
assert(CharacterCustomizationCatalog.validate_snapshot(migrated))
|
|
for style_field: String in CharacterCustomizationCatalog.FUR_STYLE_IDS:
|
|
assert(migrated[style_field] == "bengal")
|
|
|
|
var fur_colors := CharacterCustomizationCatalog.options_for("fur_pattern")
|
|
assert(fur_colors.size() == 30)
|
|
for natural_color_id: String in [
|
|
"ivory",
|
|
"sand",
|
|
"tan",
|
|
"taupe",
|
|
"black",
|
|
"dark_brown",
|
|
"chocolate",
|
|
"chestnut",
|
|
"ginger",
|
|
"sage",
|
|
"forest",
|
|
]:
|
|
assert(CharacterCustomizationCatalog.is_valid_option(
|
|
"fur_pattern",
|
|
natural_color_id,
|
|
))
|
|
var custom_color := Color("2b91c7")
|
|
var custom_color_id := CharacterCustomizationCatalog.custom_fur_color_id(
|
|
custom_color
|
|
)
|
|
assert(custom_color_id == "custom_2b91c7")
|
|
assert(
|
|
CharacterCustomizationCatalog.custom_fur_color_id(
|
|
Color(custom_color.r, custom_color.g, custom_color.b, 0.2)
|
|
)
|
|
== custom_color_id
|
|
)
|
|
for color_field: String in CharacterCustomizationCatalog.FUR_COLOR_IDS:
|
|
assert(CharacterCustomizationCatalog.is_valid_option(
|
|
color_field,
|
|
custom_color_id,
|
|
))
|
|
assert(
|
|
CharacterCustomizationCatalog.option_color(
|
|
color_field, custom_color_id
|
|
).is_equal_approx(custom_color)
|
|
)
|
|
assert(not CharacterCustomizationCatalog.is_valid_option(
|
|
"fur_pattern", "custom_2b91cg"
|
|
))
|
|
assert(not CharacterCustomizationCatalog.is_valid_option(
|
|
"fur_pattern", "custom_2b91c7ff"
|
|
))
|
|
|
|
var pattern_ids: Array[String] = []
|
|
for option: Dictionary in CharacterCustomizationCatalog.options_for(
|
|
CharacterCustomizationCatalog.FUR_STYLE_ID
|
|
):
|
|
pattern_ids.append(str(option.get("id", "")))
|
|
assert(pattern_ids == [
|
|
"solid",
|
|
"bengal",
|
|
"calico",
|
|
"fox",
|
|
"paws",
|
|
"stripes",
|
|
"tiger",
|
|
"tummy",
|
|
])
|
|
|
|
var patterned := CharacterCustomizationCatalog.default_snapshot()
|
|
patterned["species"] = "round"
|
|
patterned["ears"] = "pointy_short"
|
|
patterned["tail"] = "fox"
|
|
patterned["fur_pattern"] = "orange"
|
|
patterned[CharacterCustomizationCatalog.FUR_STYLE_ID] = "calico"
|
|
patterned[CharacterCustomizationCatalog.FUR_STYLE_ARMS_ID] = "paws"
|
|
patterned[CharacterCustomizationCatalog.FUR_STYLE_HEAD_ID] = "tiger"
|
|
patterned[CharacterCustomizationCatalog.FUR_STYLE_EARS_ID] = "fox"
|
|
patterned[CharacterCustomizationCatalog.FUR_STYLE_TAIL_ID] = "fox"
|
|
patterned["fur_color_2"] = "cream"
|
|
patterned["fur_color_3"] = "brown"
|
|
patterned["fur_color_4"] = "red"
|
|
assert(CharacterCustomizationCatalog.validate_snapshot(patterned))
|
|
|
|
_assert_option_ids(
|
|
CharacterCustomizationCatalog.fur_pattern_options_for_field(
|
|
CharacterCustomizationCatalog.FUR_STYLE_ID, patterned
|
|
),
|
|
["solid", "bengal", "calico", "fox", "paws", "stripes", "tiger", "tummy"],
|
|
)
|
|
_assert_option_ids(
|
|
CharacterCustomizationCatalog.fur_pattern_options_for_field(
|
|
CharacterCustomizationCatalog.FUR_STYLE_ARMS_ID, patterned
|
|
),
|
|
["solid", "bengal", "fox", "paws"],
|
|
)
|
|
_assert_option_ids(
|
|
CharacterCustomizationCatalog.fur_pattern_options_for_field(
|
|
CharacterCustomizationCatalog.FUR_STYLE_HEAD_ID, patterned
|
|
),
|
|
["solid", "bengal", "fox", "tiger"],
|
|
)
|
|
_assert_option_ids(
|
|
CharacterCustomizationCatalog.fur_pattern_options_for_field(
|
|
CharacterCustomizationCatalog.FUR_STYLE_EARS_ID, patterned
|
|
),
|
|
["solid", "fox"],
|
|
)
|
|
_assert_option_ids(
|
|
CharacterCustomizationCatalog.fur_pattern_options_for_field(
|
|
CharacterCustomizationCatalog.FUR_STYLE_TAIL_ID, patterned
|
|
),
|
|
["solid", "fox"],
|
|
)
|
|
var pointy_snapshot := patterned.duplicate(true)
|
|
pointy_snapshot["species"] = "pointy"
|
|
_assert_option_ids(
|
|
CharacterCustomizationCatalog.fur_pattern_options_for_field(
|
|
CharacterCustomizationCatalog.FUR_STYLE_HEAD_ID, pointy_snapshot
|
|
),
|
|
["solid", "fox"],
|
|
)
|
|
var no_accessories := patterned.duplicate(true)
|
|
no_accessories["ears"] = "none"
|
|
no_accessories["tail"] = "none"
|
|
_assert_option_ids(
|
|
CharacterCustomizationCatalog.fur_pattern_options_for_field(
|
|
CharacterCustomizationCatalog.FUR_STYLE_EARS_ID, no_accessories
|
|
),
|
|
["solid"],
|
|
)
|
|
_assert_option_ids(
|
|
CharacterCustomizationCatalog.fur_pattern_options_for_field(
|
|
CharacterCustomizationCatalog.FUR_STYLE_TAIL_ID, no_accessories
|
|
),
|
|
["solid"],
|
|
)
|
|
|
|
var body_texture := _assert_pattern_texture("calico", "body_main")
|
|
var arms_texture := _assert_pattern_texture("paws", "body_arms")
|
|
var head_texture := _assert_pattern_texture("tiger", "head_round")
|
|
var ears_texture := _assert_pattern_texture("fox", "ears_pointy_short")
|
|
var tail_texture := _assert_pattern_texture("fox", "tails_fox")
|
|
_assert_pattern_texture("tummy", "body_main")
|
|
_assert_pattern_texture("stripes", "body_main")
|
|
_assert_pattern_texture("fox", "head_pointy")
|
|
|
|
var visuals := PlayerVisualPresenter.instantiate_visuals()
|
|
root.add_child(visuals)
|
|
PlayerVisualPresenter.apply_appearance(visuals, patterned)
|
|
var skeleton := visuals.find_child("Skeleton3D", true, false) as Skeleton3D
|
|
assert(skeleton != null)
|
|
var body := skeleton.get_node_or_null("body_main") as MeshInstance3D
|
|
assert(body != null)
|
|
var body_arrays := body.mesh.surface_get_arrays(0)
|
|
var body_uvs := body_arrays[Mesh.ARRAY_TEX_UV] as PackedVector2Array
|
|
assert(not body_uvs.is_empty())
|
|
var uv_min := Vector2(INF, INF)
|
|
var uv_max := Vector2(-INF, -INF)
|
|
for uv: Vector2 in body_uvs:
|
|
uv_min = uv_min.min(uv)
|
|
uv_max = uv_max.max(uv)
|
|
assert(uv_min.x < 0.05 and uv_min.y < 0.05)
|
|
assert(uv_max.x > 0.95 and uv_max.y > 0.95)
|
|
|
|
var body_material := _assert_mesh_pattern(
|
|
skeleton, "body_main", body_texture
|
|
)
|
|
var arms_material := _assert_mesh_pattern(
|
|
skeleton, "body_arms", arms_texture
|
|
)
|
|
var head_material := _assert_mesh_pattern(
|
|
skeleton, "head_round", head_texture
|
|
)
|
|
var ears_material := _assert_mesh_pattern(
|
|
skeleton, "ears_pointy_short", ears_texture
|
|
)
|
|
var tail_material := _assert_mesh_pattern(
|
|
skeleton, "tails_fox", tail_texture
|
|
)
|
|
for material: ShaderMaterial in [
|
|
body_material,
|
|
arms_material,
|
|
head_material,
|
|
ears_material,
|
|
tail_material,
|
|
]:
|
|
assert(material.shader == preload("res://player/fur_pattern.gdshader"))
|
|
assert(material.get_shader_parameter("base_color") == Color("c86c36"))
|
|
assert(material.get_shader_parameter("red_zone_color") == Color("e6d39b"))
|
|
assert(material.get_shader_parameter("green_zone_color") == Color("7b4a32"))
|
|
assert(material.get_shader_parameter("blue_zone_color") == Color("a9433f"))
|
|
|
|
var recolored := patterned.duplicate(true)
|
|
recolored["fur_color_3"] = "teal"
|
|
PlayerVisualPresenter.apply_appearance(visuals, recolored)
|
|
for material: ShaderMaterial in [
|
|
body_material,
|
|
arms_material,
|
|
head_material,
|
|
ears_material,
|
|
tail_material,
|
|
]:
|
|
assert(material.get_shader_parameter("green_zone_color") == Color("3a8790"))
|
|
assert(
|
|
NetworkProfileProtocol.signature_fields({"appearance": patterned})
|
|
!= NetworkProfileProtocol.signature_fields({"appearance": recolored})
|
|
)
|
|
var other_arms := patterned.duplicate(true)
|
|
other_arms[CharacterCustomizationCatalog.FUR_STYLE_ARMS_ID] = "fox"
|
|
assert(
|
|
NetworkProfileProtocol.signature_fields({"appearance": patterned})
|
|
!= NetworkProfileProtocol.signature_fields({"appearance": other_arms})
|
|
)
|
|
var custom_snapshot := patterned.duplicate(true)
|
|
custom_snapshot["fur_pattern"] = custom_color_id
|
|
assert(CharacterCustomizationCatalog.validate_snapshot(custom_snapshot))
|
|
PlayerVisualPresenter.apply_appearance(visuals, custom_snapshot)
|
|
for material: ShaderMaterial in [
|
|
body_material,
|
|
arms_material,
|
|
head_material,
|
|
ears_material,
|
|
tail_material,
|
|
]:
|
|
assert(
|
|
(material.get_shader_parameter("base_color") as Color).is_equal_approx(
|
|
custom_color
|
|
)
|
|
)
|
|
assert(
|
|
NetworkProfileProtocol.signature_fields({"appearance": patterned})
|
|
!= NetworkProfileProtocol.signature_fields({"appearance": custom_snapshot})
|
|
)
|
|
|
|
var solid := CharacterCustomizationCatalog.default_snapshot()
|
|
PlayerVisualPresenter.apply_appearance(visuals, solid)
|
|
assert(body.material_override is StandardMaterial3D)
|
|
await _validate_fur_color_ui(patterned)
|
|
|
|
print("Fur pattern validation: PASS")
|
|
visuals.queue_free()
|
|
await process_frame
|
|
quit()
|
|
|
|
|
|
func _validate_fur_color_ui(snapshot: Dictionary) -> void:
|
|
var profile_page := ProfilePage.new()
|
|
profile_page.size = Vector2(1280.0, 720.0)
|
|
root.add_child(profile_page)
|
|
await process_frame
|
|
profile_page.set("_draft_appearance", snapshot.duplicate(true))
|
|
profile_page.call("_select_category", "fur_pattern")
|
|
profile_page.call("_select_fur_section", "colors")
|
|
await process_frame
|
|
await process_frame
|
|
var color_panel := profile_page.find_child(
|
|
"FurColorOptionsPanel", true, false
|
|
) as PanelContainer
|
|
var channel_grid := profile_page.find_child(
|
|
"FurColorChannelGrid", true, false
|
|
) as GridContainer
|
|
var palette_grid := profile_page.find_child(
|
|
"FurPaletteGrid", true, false
|
|
) as GridContainer
|
|
var picker := profile_page.find_child(
|
|
"FurCustomColorPicker", true, false
|
|
) as ColorPickerButton
|
|
var picker_popup := picker.get_popup()
|
|
var picker_control := picker.get_picker()
|
|
assert(color_panel != null and color_panel.size.x > 360.0)
|
|
assert(channel_grid != null and channel_grid.columns == 4)
|
|
assert(channel_grid.get_child_count() == 4)
|
|
assert(palette_grid != null and palette_grid.columns == 10)
|
|
assert(palette_grid.get_child_count() == 30)
|
|
for option_button: Button in palette_grid.get_children():
|
|
assert(
|
|
option_button.custom_minimum_size
|
|
== Vector2.ONE * ProfilePage.FUR_PALETTE_SWATCH_SIZE
|
|
)
|
|
for color_index: int in CharacterCustomizationCatalog.FUR_COLOR_IDS.size():
|
|
var category_id: String = (
|
|
CharacterCustomizationCatalog.FUR_COLOR_IDS[color_index]
|
|
)
|
|
var channel_button := profile_page.find_child(
|
|
"FurColorSlot%d" % (color_index + 1), true, false
|
|
) as Button
|
|
assert(channel_button != null)
|
|
var swatch := channel_button.get_node(
|
|
"ChannelContents/ActiveColorSwatch"
|
|
) as Panel
|
|
var channel_label := channel_button.get_node(
|
|
"ChannelContents/ChannelNumber"
|
|
) as Label
|
|
assert(swatch.size.is_equal_approx(
|
|
Vector2.ONE * ProfilePage.FUR_CHANNEL_SWATCH_SIZE
|
|
))
|
|
assert(swatch.size.y <= channel_label.size.y)
|
|
var swatch_style := swatch.get_theme_stylebox(
|
|
"panel"
|
|
) as StyleBoxFlat
|
|
assert(swatch_style != null)
|
|
assert(
|
|
swatch_style.corner_radius_top_left
|
|
== roundi(ProfilePage.FUR_CHANNEL_SWATCH_SIZE * 0.5)
|
|
)
|
|
assert(swatch_style.bg_color.is_equal_approx(
|
|
CharacterCustomizationCatalog.option_color(
|
|
category_id,
|
|
str(snapshot[category_id]),
|
|
)
|
|
))
|
|
var picker_style := picker.get_theme_stylebox("normal") as StyleBoxFlat
|
|
assert(picker_style != null and picker_style.corner_radius_top_left == 14)
|
|
assert(picker_popup.min_size == ProfilePage.FUR_COLOR_PICKER_POPUP_SIZE)
|
|
assert(picker_popup.theme.default_font == UtilityPageStyle.TuffyFont)
|
|
var popup_style := picker_popup.get_theme_stylebox(
|
|
"panel"
|
|
) as StyleBoxFlat
|
|
assert(popup_style != null)
|
|
assert(popup_style.bg_color.a == 1.0)
|
|
assert(popup_style.corner_radius_top_left == 20)
|
|
assert(
|
|
picker_control.get_theme_constant("sv_width")
|
|
== ProfilePage.FUR_COLOR_PICKER_SV_SIZE.x
|
|
)
|
|
assert(
|
|
picker_control.get_theme_constant("sv_height")
|
|
== ProfilePage.FUR_COLOR_PICKER_SV_SIZE.y
|
|
)
|
|
assert(not picker_control.edit_alpha)
|
|
assert(not picker_control.edit_intensity)
|
|
var picker_display := picker.get_node("FurCustomColorDisplay") as Panel
|
|
var display_style := picker_display.get_theme_stylebox(
|
|
"panel"
|
|
) as StyleBoxFlat
|
|
assert(display_style != null and display_style.corner_radius_top_left == 11)
|
|
var live_custom_color := Color("2b91c7")
|
|
profile_page.call(
|
|
"_select_custom_fur_color",
|
|
live_custom_color,
|
|
CharacterCustomizationCatalog.FUR_COLOR_IDS[0],
|
|
)
|
|
var first_swatch := profile_page.find_child(
|
|
"FurColorSlot1", true, false
|
|
).get_node("ChannelContents/ActiveColorSwatch") as Panel
|
|
var first_swatch_style := first_swatch.get_theme_stylebox(
|
|
"panel"
|
|
) as StyleBoxFlat
|
|
assert(first_swatch_style.bg_color.is_equal_approx(live_custom_color))
|
|
profile_page.free()
|
|
await process_frame
|
|
|
|
|
|
func _assert_option_ids(options: Array, expected: Array[String]) -> void:
|
|
var actual: Array[String] = []
|
|
for option: Dictionary in options:
|
|
actual.append(str(option.get("id", "")))
|
|
assert(actual == expected)
|
|
|
|
|
|
func _assert_pattern_texture(
|
|
pattern_id: String,
|
|
component_id: String,
|
|
) -> Texture2D:
|
|
var texture := CharacterCustomizationCatalog.fur_pattern_texture(
|
|
pattern_id, component_id
|
|
)
|
|
assert(texture != null)
|
|
assert(texture.get_width() == 1024)
|
|
assert(texture.get_height() == 1024)
|
|
return texture
|
|
|
|
|
|
func _assert_mesh_pattern(
|
|
skeleton: Skeleton3D,
|
|
mesh_name: String,
|
|
expected_texture: Texture2D,
|
|
) -> ShaderMaterial:
|
|
var mesh := skeleton.get_node_or_null(mesh_name) as MeshInstance3D
|
|
assert(mesh != null)
|
|
var material := mesh.material_override as ShaderMaterial
|
|
assert(material != null)
|
|
assert(material.get_shader_parameter("pattern_texture") == expected_texture)
|
|
return material
|