Expand per-part fur customization
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.
BIN
art/exported/characters/patterns/calico/body_main_calico.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://m1ujlrpjghq2"
|
||||
path="res://.godot/imported/body_main_calico.png-c5644e883b1aaedc3c3114ce40eaae02.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/characters/patterns/calico/body_main_calico.png"
|
||||
dest_files=["res://.godot/imported/body_main_calico.png-c5644e883b1aaedc3c3114ce40eaae02.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
art/exported/characters/patterns/fox/ears_pointy_short_fox.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bawjitj68qe5s"
|
||||
path="res://.godot/imported/ears_pointy_short_fox.png-a42bd3e51180136284f946a738c44a1d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/characters/patterns/fox/ears_pointy_short_fox.png"
|
||||
dest_files=["res://.godot/imported/ears_pointy_short_fox.png-a42bd3e51180136284f946a738c44a1d.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
BIN
art/exported/characters/patterns/paws/body_arms_paws.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dmqpy81ves1g8"
|
||||
path="res://.godot/imported/body_arms_paws.png-06ae28eef9d8a116856aac8b0c511466.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/characters/patterns/paws/body_arms_paws.png"
|
||||
dest_files=["res://.godot/imported/body_arms_paws.png-06ae28eef9d8a116856aac8b0c511466.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
art/exported/characters/patterns/paws/body_main_paws.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://nl50my4whybb"
|
||||
path="res://.godot/imported/body_main_paws.png-7262be81b419a66af0dc6fa26e01a7f8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/characters/patterns/paws/body_main_paws.png"
|
||||
dest_files=["res://.godot/imported/body_main_paws.png-7262be81b419a66af0dc6fa26e01a7f8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
art/exported/characters/patterns/stripes/body_main_stripes.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cmmemafdvgw5y"
|
||||
path="res://.godot/imported/body_main_stripes.png-2ff942bebaea90b5e13ff6f122820689.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/characters/patterns/stripes/body_main_stripes.png"
|
||||
dest_files=["res://.godot/imported/body_main_stripes.png-2ff942bebaea90b5e13ff6f122820689.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
art/exported/characters/patterns/tiger/body_main_tiger.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dbakek50irjdp"
|
||||
path="res://.godot/imported/body_main_tiger.png-30e81e0d87ce0d9af9d678c10fd2e5bd.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/characters/patterns/tiger/body_main_tiger.png"
|
||||
dest_files=["res://.godot/imported/body_main_tiger.png-30e81e0d87ce0d9af9d678c10fd2e5bd.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
art/exported/characters/patterns/tiger/head_round_tiger.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cnpjuifjm0h4i"
|
||||
path="res://.godot/imported/head_round_tiger.png-fe9ec3d30703aa477bff0311b12fc252.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/characters/patterns/tiger/head_round_tiger.png"
|
||||
dest_files=["res://.godot/imported/head_round_tiger.png-fe9ec3d30703aa477bff0311b12fc252.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
art/exported/characters/patterns/tummy/body_main_tummy.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dv1bran6gqbd5"
|
||||
path="res://.godot/imported/body_main_tummy.png-74bd92fbb462e4dbe1c1bf841db71eb0.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/characters/patterns/tummy/body_main_tummy.png"
|
||||
dest_files=["res://.godot/imported/body_main_tummy.png-74bd92fbb462e4dbe1c1bf841db71eb0.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
@ -176,11 +176,19 @@ authored `mesh_style.png` filenames.
|
|||
| Bengal spots, body and arms | `body_arms_bengal.png` | `art/exported/characters/patterns/bengal/body_arms_bengal.png` | `fa90bd4e74b0b770ce97bf69d069676f5d4bd1f5d649f89e09638a66b61a8470` |
|
||||
| Bengal spots, main body | `body_main_bengal.png` | `art/exported/characters/patterns/bengal/body_main_bengal.png` | `8662fa198220cfdbe0aa83bdeeef7b855e20124d74f8481467f750d32b67ee8b` |
|
||||
| Bengal spots, round head | `head_round_bengal.png` | `art/exported/characters/patterns/bengal/head_round_bengal.png` | `f397ed8d152e3fda8181757a2bbd01925b7b8c2c4362f515d91c7f0fbf3ddfb6` |
|
||||
| Calico, main body | `body_main_calico.png` | `art/exported/characters/patterns/calico/body_main_calico.png` | `25b2389ee729a6bafafbe6b5440541b18f3c5db2808ca8d49c2d5c4d012d390c` |
|
||||
| Fox, body and arms | `body_arms_fox.png` | `art/exported/characters/patterns/fox/body_arms_fox.png` | `9930808718f31d84b415902f1ba55ac2a508bb2ac127300e793d73a8a013022d` |
|
||||
| Fox, main body | `body_main_fox.png` | `art/exported/characters/patterns/fox/body_main_fox.png` | `c65e4390173b8c31c9396edd053f940f0f809e47e01d18042cd7babc712dde9d` |
|
||||
| Fox, short pointy ears | `ears_pointy_short_fox.png` | `art/exported/characters/patterns/fox/ears_pointy_short_fox.png` | `13cb35192d25c0136df287434fa056ce990698fc47dc50445c941f92a4ff42fb` |
|
||||
| Fox, pointy head | `head_pointy_fox.png` | `art/exported/characters/patterns/fox/head_pointy_fox.png` | `71a9d2383e62884dbd3981460fcd985b18805fa907f61ffb16dce08ff9418ddf` |
|
||||
| Fox, round head | `head_round_fox.png` | `art/exported/characters/patterns/fox/head_round_fox.png` | `86dd7af8a2346faf67359ad668a117a74e2408676137d6b0626403cfcf2aba73` |
|
||||
| Fox, round head | `head_round_fox.png` | `art/exported/characters/patterns/fox/head_round_fox.png` | `1ac3d503bc1e47a5ae3c1a8af719c9d1be06aab2d6d4d30c179c2a021fe7d34d` |
|
||||
| Fox tail | `tails_fox_fox.png` | `art/exported/characters/patterns/fox/tails_fox_fox.png` | `72da9673f038e2c3bb8aca19ed99932085fa3b162cab95366901c4e97153dd3e` |
|
||||
| Paws, body and arms | `body_arms_paws.png` | `art/exported/characters/patterns/paws/body_arms_paws.png` | `38f31ecf7045510501bdd08cbc243065c2fadec481462671dd22b13e8a196e66` |
|
||||
| Paws, main body | `body_main_paws.png` | `art/exported/characters/patterns/paws/body_main_paws.png` | `d52012d4a1ea3ed544582be714469ed8fec148ccd59417dda21e89c18509321b` |
|
||||
| Stripes, main body | `body_main_stripes.png` | `art/exported/characters/patterns/stripes/body_main_stripes.png` | `4b1e5b412a7af3e6acd0a5bfb1b6ffd6e0814850a311fc8cc3061419a8f23555` |
|
||||
| Tiger, main body | `body_main_tiger.png` | `art/exported/characters/patterns/tiger/body_main_tiger.png` | `64b6f47871802e72ba6fe15577f14949a760c6d44bb962bfbbdcddc991b2105b` |
|
||||
| Tiger, round head | `head_round_tiger.png` | `art/exported/characters/patterns/tiger/head_round_tiger.png` | `6af27056b9827391a73b4ea6f3736b308fa93a03127570f90cc4d3701afd3d80` |
|
||||
| Tummy, main body | `body_main_tummy.png` | `art/exported/characters/patterns/tummy/body_main_tummy.png` | `dad404614c3feae4082537c02556781ea46c664327e0dc4528804f76619af86a` |
|
||||
|
||||
### Tekgator shop icon contribution record
|
||||
|
||||
|
|
|
|||
|
|
@ -43,27 +43,14 @@ static func valid_apply_request(data: Variant) -> bool:
|
|||
|
||||
static func signature_fields(data: Dictionary) -> Array:
|
||||
var appearance: Dictionary = data.get("appearance", {})
|
||||
return [
|
||||
var result: Array = [
|
||||
str(data.get("session_id", "")),
|
||||
str(data.get("request_id", "")),
|
||||
str(data.get("sender_fingerprint", "")),
|
||||
str(data.get("display_name", "")),
|
||||
str(appearance.get("species", "")),
|
||||
CharacterCustomizationCatalog.character_scale_percent(
|
||||
appearance.get(
|
||||
CharacterCustomizationCatalog.SCALE_CATEGORY_ID,
|
||||
CharacterCustomizationCatalog.DEFAULT_CHARACTER_SCALE,
|
||||
)
|
||||
),
|
||||
str(appearance.get("fur_pattern", "")),
|
||||
str(appearance.get("fur_style", "")),
|
||||
str(appearance.get("fur_color_2", "")),
|
||||
str(appearance.get("fur_color_3", "")),
|
||||
str(appearance.get("fur_color_4", "")),
|
||||
str(appearance.get("ears", "")),
|
||||
str(appearance.get("eyes", "")),
|
||||
str(appearance.get("nose", "")),
|
||||
str(appearance.get("mouth", "")),
|
||||
str(appearance.get("tail", "")),
|
||||
bool(data.get("use_anyway", false)),
|
||||
]
|
||||
result.append_array(
|
||||
CharacterCustomizationCatalog.appearance_signature_values(appearance)
|
||||
)
|
||||
result.append(bool(data.get("use_anyway", false)))
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
class_name NetworkProtocol
|
||||
extends RefCounted
|
||||
|
||||
const PROTOCOL_VERSION: int = 5
|
||||
const PROTOCOL_VERSION: int = 6
|
||||
const GAME_BUILD: String = "prealpha"
|
||||
const MAX_GAME_VERSION_LENGTH: int = 64
|
||||
const MAX_DISPLAY_NAME_LENGTH: int = 24
|
||||
|
|
@ -256,30 +256,17 @@ static func validate_client_hello(data: Variant) -> String:
|
|||
|
||||
static func client_profile_fields(data: Dictionary) -> Array:
|
||||
var appearance: Dictionary = data.get("cosmetic_snapshot", {})
|
||||
return [
|
||||
var result: Array = [
|
||||
str(data.get("game_version", "")),
|
||||
str(data.get("client_nonce", "")),
|
||||
str(data.get("identity_fingerprint", "")),
|
||||
str(data.get("local_profile_id", "")),
|
||||
str(data.get("display_name", "")),
|
||||
str(appearance.get("species", "")),
|
||||
CharacterCustomizationCatalog.character_scale_percent(
|
||||
appearance.get(
|
||||
CharacterCustomizationCatalog.SCALE_CATEGORY_ID,
|
||||
CharacterCustomizationCatalog.DEFAULT_CHARACTER_SCALE,
|
||||
)
|
||||
),
|
||||
str(appearance.get("fur_pattern", "")),
|
||||
str(appearance.get("fur_style", "")),
|
||||
str(appearance.get("fur_color_2", "")),
|
||||
str(appearance.get("fur_color_3", "")),
|
||||
str(appearance.get("fur_color_4", "")),
|
||||
str(appearance.get("ears", "")),
|
||||
str(appearance.get("eyes", "")),
|
||||
str(appearance.get("nose", "")),
|
||||
str(appearance.get("mouth", "")),
|
||||
str(appearance.get("tail", "")),
|
||||
]
|
||||
result.append_array(
|
||||
CharacterCustomizationCatalog.appearance_signature_values(appearance)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
static func make_server_hello(
|
||||
|
|
|
|||
|
|
@ -218,10 +218,13 @@ static func _set_fur_mesh(
|
|||
var mesh_instance := skeleton.get_node_or_null(mesh_name) as MeshInstance3D
|
||||
if mesh_instance == null:
|
||||
return
|
||||
var style_field := (
|
||||
CharacterCustomizationCatalog.fur_style_field_for_component(mesh_name)
|
||||
)
|
||||
var style_id := CharacterCustomizationCatalog.canonical_option_id(
|
||||
CharacterCustomizationCatalog.FUR_STYLE_ID,
|
||||
style_field,
|
||||
str(snapshot.get(
|
||||
CharacterCustomizationCatalog.FUR_STYLE_ID,
|
||||
style_field,
|
||||
CharacterCustomizationCatalog.DEFAULT_FUR_STYLE,
|
||||
)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ const SNAPSHOT_IDS: PackedStringArray = [
|
|||
"scale",
|
||||
"fur_pattern",
|
||||
"fur_style",
|
||||
"fur_style_arms",
|
||||
"fur_style_head",
|
||||
"fur_style_ears",
|
||||
"fur_style_tail",
|
||||
"fur_color_2",
|
||||
"fur_color_3",
|
||||
"fur_color_4",
|
||||
|
|
@ -47,6 +51,24 @@ const MAX_CHARACTER_SCALE: float = 1.5
|
|||
const DEFAULT_CHARACTER_SCALE: float = 1.0
|
||||
const CHARACTER_SCALE_STEP: float = 0.05
|
||||
const FUR_STYLE_ID: String = "fur_style"
|
||||
const FUR_STYLE_ARMS_ID: String = "fur_style_arms"
|
||||
const FUR_STYLE_HEAD_ID: String = "fur_style_head"
|
||||
const FUR_STYLE_EARS_ID: String = "fur_style_ears"
|
||||
const FUR_STYLE_TAIL_ID: String = "fur_style_tail"
|
||||
const FUR_STYLE_IDS: PackedStringArray = [
|
||||
FUR_STYLE_ID,
|
||||
FUR_STYLE_ARMS_ID,
|
||||
FUR_STYLE_HEAD_ID,
|
||||
FUR_STYLE_EARS_ID,
|
||||
FUR_STYLE_TAIL_ID,
|
||||
]
|
||||
const FUR_STYLE_LABELS: Dictionary = {
|
||||
FUR_STYLE_ID: "body",
|
||||
FUR_STYLE_ARMS_ID: "arms",
|
||||
FUR_STYLE_HEAD_ID: "head",
|
||||
FUR_STYLE_EARS_ID: "ears",
|
||||
FUR_STYLE_TAIL_ID: "tail",
|
||||
}
|
||||
const FUR_COLOR_IDS: PackedStringArray = [
|
||||
"fur_pattern",
|
||||
"fur_color_2",
|
||||
|
|
@ -56,10 +78,14 @@ const FUR_COLOR_IDS: PackedStringArray = [
|
|||
const FUR_COLOR_LABELS: Dictionary = {
|
||||
"fur_pattern": "primary",
|
||||
"fur_color_2": "secondary",
|
||||
"fur_color_3": "accent",
|
||||
"fur_color_4": "detail",
|
||||
"fur_color_3": "tertiary",
|
||||
"fur_color_4": "quaternary",
|
||||
}
|
||||
const CUSTOM_FUR_COLOR_PREFIX: String = "custom_"
|
||||
const DEFAULT_FUR_STYLE: String = "solid"
|
||||
const FUR_PATTERN_ASSET_ROOT: String = (
|
||||
"res://art/exported/characters/patterns"
|
||||
)
|
||||
const FEATURE_TEXTURE_CACHE_LIMIT: int = 24
|
||||
|
||||
const FEATURE_CATEGORIES: PackedStringArray = ["eyes", "nose", "mouth"]
|
||||
|
|
@ -117,53 +143,6 @@ const OPTIONS: Dictionary = {
|
|||
"fur_color_2": FUR_COLOR_OPTIONS,
|
||||
"fur_color_3": FUR_COLOR_OPTIONS,
|
||||
"fur_color_4": FUR_COLOR_OPTIONS,
|
||||
"fur_style": [
|
||||
{"id": "solid", "label": "solid"},
|
||||
{
|
||||
"id": "spots_bengal",
|
||||
"label": "bengal spots",
|
||||
"textures": {
|
||||
"body_main": (
|
||||
"res://art/exported/characters/patterns/"
|
||||
+ "bengal/body_main_bengal.png"
|
||||
),
|
||||
"body_arms": (
|
||||
"res://art/exported/characters/patterns/"
|
||||
+ "bengal/body_arms_bengal.png"
|
||||
),
|
||||
"head_round": (
|
||||
"res://art/exported/characters/patterns/"
|
||||
+ "bengal/head_round_bengal.png"
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "fox",
|
||||
"label": "fox",
|
||||
"textures": {
|
||||
"body_main": (
|
||||
"res://art/exported/characters/patterns/"
|
||||
+ "fox/body_main_fox.png"
|
||||
),
|
||||
"body_arms": (
|
||||
"res://art/exported/characters/patterns/"
|
||||
+ "fox/body_arms_fox.png"
|
||||
),
|
||||
"head_pointy": (
|
||||
"res://art/exported/characters/patterns/"
|
||||
+ "fox/head_pointy_fox.png"
|
||||
),
|
||||
"head_round": (
|
||||
"res://art/exported/characters/patterns/"
|
||||
+ "fox/head_round_fox.png"
|
||||
),
|
||||
"tails_fox": (
|
||||
"res://art/exported/characters/patterns/"
|
||||
+ "fox/tails_fox_fox.png"
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
"ears": [
|
||||
{"id": "none", "label": "none"},
|
||||
{"id": "antlers_round", "label": "antlers"},
|
||||
|
|
@ -196,7 +175,7 @@ const OPTIONS: Dictionary = {
|
|||
const LEGACY_OPTION_ALIASES: Dictionary = {
|
||||
"species": {"default": "round"},
|
||||
"fur_pattern": {"solid": "white"},
|
||||
"fur_style": {"bengal": "spots_bengal"},
|
||||
"fur_style": {"spots_bengal": "bengal"},
|
||||
"ears": {"default": "none"},
|
||||
"tail": {"default": "none"},
|
||||
"eyes": {"default": "simple_shine"},
|
||||
|
|
@ -209,6 +188,9 @@ static var _feature_options: Dictionary = {}
|
|||
static var _feature_resource_paths: Dictionary = {}
|
||||
static var _feature_textures: Dictionary = {}
|
||||
static var _feature_texture_cache_order: Array[String] = []
|
||||
static var _fur_pattern_assets_ready: bool = false
|
||||
static var _fur_pattern_options: Array = []
|
||||
static var _fur_pattern_resource_paths: Dictionary = {}
|
||||
static var _fur_pattern_textures: Dictionary = {}
|
||||
|
||||
|
||||
|
|
@ -219,6 +201,10 @@ static func default_snapshot() -> Dictionary:
|
|||
"scale": DEFAULT_CHARACTER_SCALE,
|
||||
"fur_pattern": "white",
|
||||
"fur_style": DEFAULT_FUR_STYLE,
|
||||
"fur_style_arms": DEFAULT_FUR_STYLE,
|
||||
"fur_style_head": DEFAULT_FUR_STYLE,
|
||||
"fur_style_ears": DEFAULT_FUR_STYLE,
|
||||
"fur_style_tail": DEFAULT_FUR_STYLE,
|
||||
"fur_color_2": "cream",
|
||||
"fur_color_3": "brown",
|
||||
"fur_color_4": "red",
|
||||
|
|
@ -249,6 +235,9 @@ static func options_for(category_id: String) -> Array:
|
|||
if category_id in FEATURE_CATEGORIES:
|
||||
_ensure_feature_assets()
|
||||
return _feature_options.get(category_id, []) as Array
|
||||
if category_id in FUR_STYLE_IDS:
|
||||
_ensure_fur_pattern_assets()
|
||||
return _fur_pattern_options
|
||||
return OPTIONS.get(category_id, [])
|
||||
|
||||
|
||||
|
|
@ -386,6 +375,98 @@ static func refresh_feature_assets() -> void:
|
|||
_ensure_feature_assets()
|
||||
|
||||
|
||||
static func refresh_fur_pattern_assets() -> void:
|
||||
_fur_pattern_assets_ready = false
|
||||
_fur_pattern_options.clear()
|
||||
_fur_pattern_resource_paths.clear()
|
||||
_fur_pattern_textures.clear()
|
||||
_ensure_fur_pattern_assets()
|
||||
|
||||
|
||||
static func _ensure_fur_pattern_assets() -> void:
|
||||
if _fur_pattern_assets_ready:
|
||||
return
|
||||
_fur_pattern_assets_ready = true
|
||||
_fur_pattern_options = [{"id": DEFAULT_FUR_STYLE, "label": "solid"}]
|
||||
var root_directory := DirAccess.open(FUR_PATTERN_ASSET_ROOT)
|
||||
if root_directory == null:
|
||||
return
|
||||
var directory_names := root_directory.get_directories()
|
||||
directory_names.sort()
|
||||
for directory_name: String in directory_names:
|
||||
var pattern_id := _normalized_asset_id(directory_name)
|
||||
if pattern_id.is_empty() or pattern_id == DEFAULT_FUR_STYLE:
|
||||
continue
|
||||
var pattern_directory_path := FUR_PATTERN_ASSET_ROOT.path_join(
|
||||
directory_name
|
||||
)
|
||||
var pattern_directory := DirAccess.open(pattern_directory_path)
|
||||
if pattern_directory == null:
|
||||
continue
|
||||
var file_names := pattern_directory.get_files()
|
||||
file_names.sort()
|
||||
var canonical_files: Dictionary = {}
|
||||
var found_pattern_texture := false
|
||||
for file_name: String in file_names:
|
||||
var canonical_file_name := _canonical_feature_filename(file_name)
|
||||
if (
|
||||
canonical_file_name.is_empty()
|
||||
or canonical_files.has(canonical_file_name)
|
||||
):
|
||||
continue
|
||||
canonical_files[canonical_file_name] = true
|
||||
var file_base_name := canonical_file_name.get_basename().to_lower()
|
||||
var pattern_suffix := "_" + pattern_id
|
||||
if not file_base_name.ends_with(pattern_suffix):
|
||||
continue
|
||||
var component_id := file_base_name.substr(
|
||||
0,
|
||||
file_base_name.length() - pattern_suffix.length(),
|
||||
)
|
||||
component_id = _normalized_asset_id(component_id)
|
||||
if component_id.is_empty():
|
||||
continue
|
||||
var resource_path := pattern_directory_path.path_join(
|
||||
canonical_file_name
|
||||
)
|
||||
if not ResourceLoader.exists(resource_path, "Texture2D"):
|
||||
push_warning(
|
||||
"Ignoring fur mask without a loadable texture: "
|
||||
+ resource_path
|
||||
)
|
||||
continue
|
||||
_fur_pattern_resource_paths[
|
||||
_fur_pattern_cache_id(pattern_id, component_id)
|
||||
] = resource_path
|
||||
found_pattern_texture = true
|
||||
if found_pattern_texture:
|
||||
_fur_pattern_options.append({
|
||||
"id": pattern_id,
|
||||
"label": pattern_id.replace("_", " "),
|
||||
})
|
||||
|
||||
|
||||
static func _normalized_asset_id(value: String) -> String:
|
||||
var result := value.to_lower()
|
||||
result = result.replace("-", "_")
|
||||
result = result.replace(" ", "_")
|
||||
result = result.replace(".", "_")
|
||||
while result.contains("__"):
|
||||
result = result.replace("__", "_")
|
||||
while result.begins_with("_"):
|
||||
result = result.substr(1)
|
||||
while result.ends_with("_"):
|
||||
result = result.substr(0, result.length() - 1)
|
||||
return result
|
||||
|
||||
|
||||
static func _fur_pattern_cache_id(
|
||||
pattern_id: String,
|
||||
component_id: String,
|
||||
) -> String:
|
||||
return "%s:%s" % [pattern_id, component_id]
|
||||
|
||||
|
||||
static func _ensure_feature_assets() -> void:
|
||||
if _feature_assets_ready:
|
||||
return
|
||||
|
|
@ -473,6 +554,8 @@ static func category_label(category_id: String) -> String:
|
|||
|
||||
static func is_valid_option(category_id: String, option_id: String) -> bool:
|
||||
option_id = canonical_option_id(category_id, option_id)
|
||||
if category_id in FUR_COLOR_IDS and is_custom_fur_color_id(option_id):
|
||||
return true
|
||||
for option: Dictionary in options_for(category_id):
|
||||
if str(option.get("id", "")) == option_id:
|
||||
return true
|
||||
|
|
@ -480,44 +563,149 @@ static func is_valid_option(category_id: String, option_id: String) -> bool:
|
|||
|
||||
|
||||
static func canonical_option_id(category_id: String, option_id: String) -> String:
|
||||
var aliases: Dictionary = LEGACY_OPTION_ALIASES.get(category_id, {})
|
||||
if (
|
||||
category_id in FUR_COLOR_IDS
|
||||
and option_id.to_lower().begins_with(CUSTOM_FUR_COLOR_PREFIX)
|
||||
):
|
||||
return option_id.to_lower()
|
||||
var alias_category_id := (
|
||||
FUR_STYLE_ID if category_id in FUR_STYLE_IDS else category_id
|
||||
)
|
||||
var aliases: Dictionary = LEGACY_OPTION_ALIASES.get(alias_category_id, {})
|
||||
return str(aliases.get(option_id, option_id))
|
||||
|
||||
|
||||
static func option_color(category_id: String, option_id: String) -> Color:
|
||||
var canonical_id: String = canonical_option_id(category_id, option_id)
|
||||
if category_id in FUR_COLOR_IDS and is_custom_fur_color_id(canonical_id):
|
||||
return Color(canonical_id.trim_prefix(CUSTOM_FUR_COLOR_PREFIX))
|
||||
for option: Dictionary in options_for(category_id):
|
||||
if str(option.get("id", "")) == canonical_id:
|
||||
return option.get("color", Color.WHITE) as Color
|
||||
return Color.WHITE
|
||||
|
||||
|
||||
static func custom_fur_color_id(color: Color) -> String:
|
||||
var opaque_color := Color(color.r, color.g, color.b, 1.0)
|
||||
return CUSTOM_FUR_COLOR_PREFIX + opaque_color.to_html(false)
|
||||
|
||||
|
||||
static func is_custom_fur_color_id(option_id: String) -> bool:
|
||||
var canonical_id := option_id.to_lower()
|
||||
if (
|
||||
canonical_id.length() != CUSTOM_FUR_COLOR_PREFIX.length() + 6
|
||||
or not canonical_id.begins_with(CUSTOM_FUR_COLOR_PREFIX)
|
||||
):
|
||||
return false
|
||||
var hex_value := canonical_id.trim_prefix(CUSTOM_FUR_COLOR_PREFIX)
|
||||
for character_index: int in range(hex_value.length()):
|
||||
if hex_value[character_index] not in "0123456789abcdef":
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
static func fur_color_label(category_id: String) -> String:
|
||||
return str(FUR_COLOR_LABELS.get(category_id, category_id))
|
||||
|
||||
|
||||
static func fur_style_label(category_id: String) -> String:
|
||||
return str(FUR_STYLE_LABELS.get(category_id, category_id))
|
||||
|
||||
|
||||
static func fur_style_field_for_component(component_id: String) -> String:
|
||||
if component_id == "body_arms":
|
||||
return FUR_STYLE_ARMS_ID
|
||||
if component_id.begins_with("head_"):
|
||||
return FUR_STYLE_HEAD_ID
|
||||
if component_id.begins_with("ears_"):
|
||||
return FUR_STYLE_EARS_ID
|
||||
if component_id.begins_with("tails_"):
|
||||
return FUR_STYLE_TAIL_ID
|
||||
return FUR_STYLE_ID
|
||||
|
||||
|
||||
static func fur_component_for_style_field(
|
||||
category_id: String,
|
||||
snapshot: Dictionary,
|
||||
) -> String:
|
||||
match category_id:
|
||||
FUR_STYLE_ID:
|
||||
return "body_main"
|
||||
FUR_STYLE_ARMS_ID:
|
||||
return "body_arms"
|
||||
FUR_STYLE_HEAD_ID:
|
||||
var species_id := canonical_option_id(
|
||||
"species", str(snapshot.get("species", "round"))
|
||||
)
|
||||
return "head_pointy" if species_id == "pointy" else "head_round"
|
||||
FUR_STYLE_EARS_ID:
|
||||
var ears_id := canonical_option_id(
|
||||
"ears", str(snapshot.get("ears", "none"))
|
||||
)
|
||||
return "" if ears_id == "none" else "ears_" + ears_id
|
||||
FUR_STYLE_TAIL_ID:
|
||||
var tail_id := canonical_option_id(
|
||||
"tail", str(snapshot.get("tail", "none"))
|
||||
)
|
||||
return "" if tail_id == "none" else "tails_" + tail_id
|
||||
return ""
|
||||
|
||||
|
||||
static func fur_pattern_options_for_field(
|
||||
category_id: String,
|
||||
snapshot: Dictionary,
|
||||
) -> Array:
|
||||
if category_id not in FUR_STYLE_IDS:
|
||||
return []
|
||||
_ensure_fur_pattern_assets()
|
||||
var component_id := fur_component_for_style_field(category_id, snapshot)
|
||||
var result: Array = []
|
||||
for option: Dictionary in _fur_pattern_options:
|
||||
var pattern_id := str(option.get("id", ""))
|
||||
if (
|
||||
pattern_id == DEFAULT_FUR_STYLE
|
||||
or (
|
||||
not component_id.is_empty()
|
||||
and _fur_pattern_resource_paths.has(
|
||||
_fur_pattern_cache_id(pattern_id, component_id)
|
||||
)
|
||||
)
|
||||
):
|
||||
result.append(option)
|
||||
return result
|
||||
|
||||
|
||||
static func fur_pattern_texture(
|
||||
option_id: String,
|
||||
component_id: String = "body_main",
|
||||
) -> Texture2D:
|
||||
_ensure_fur_pattern_assets()
|
||||
var canonical_id := canonical_option_id(FUR_STYLE_ID, option_id)
|
||||
if canonical_id == DEFAULT_FUR_STYLE:
|
||||
return null
|
||||
var cache_id := "%s:%s" % [canonical_id, component_id]
|
||||
var cache_id := _fur_pattern_cache_id(canonical_id, component_id)
|
||||
if _fur_pattern_textures.has(cache_id):
|
||||
return _fur_pattern_textures[cache_id] as Texture2D
|
||||
for option: Dictionary in options_for(FUR_STYLE_ID):
|
||||
if str(option.get("id", "")) != canonical_id:
|
||||
continue
|
||||
var textures := option.get("textures", {}) as Dictionary
|
||||
var resource_path := str(textures.get(component_id, ""))
|
||||
var resource_path := str(_fur_pattern_resource_paths.get(cache_id, ""))
|
||||
if resource_path.is_empty():
|
||||
return null
|
||||
var texture := ResourceLoader.load(resource_path) as Texture2D
|
||||
var texture := ResourceLoader.load(resource_path, "Texture2D") as Texture2D
|
||||
if texture != null:
|
||||
_fur_pattern_textures[cache_id] = texture
|
||||
return texture
|
||||
return null
|
||||
|
||||
|
||||
static func appearance_signature_values(snapshot: Dictionary) -> Array:
|
||||
var result: Array = []
|
||||
for category_id: String in SNAPSHOT_IDS:
|
||||
if category_id == SCALE_CATEGORY_ID:
|
||||
result.append(character_scale_percent(snapshot.get(
|
||||
category_id,
|
||||
DEFAULT_CHARACTER_SCALE,
|
||||
)))
|
||||
else:
|
||||
result.append(str(snapshot.get(category_id, "")))
|
||||
return result
|
||||
|
||||
|
||||
static func validate_snapshot(value: Variant) -> bool:
|
||||
|
|
@ -550,8 +738,15 @@ static func sanitized_snapshot(value: Variant) -> Dictionary:
|
|||
snapshot.get(category_id, DEFAULT_CHARACTER_SCALE)
|
||||
)
|
||||
continue
|
||||
var raw_option: Variant = snapshot.get(category_id, "")
|
||||
if (
|
||||
category_id in FUR_STYLE_IDS
|
||||
and category_id != FUR_STYLE_ID
|
||||
and not snapshot.has(category_id)
|
||||
):
|
||||
raw_option = snapshot.get(FUR_STYLE_ID, DEFAULT_FUR_STYLE)
|
||||
var option_id: String = canonical_option_id(
|
||||
category_id, str(snapshot.get(category_id, ""))
|
||||
category_id, str(raw_option)
|
||||
)
|
||||
if is_valid_option(category_id, option_id):
|
||||
result[category_id] = option_id
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ func _run() -> void:
|
|||
var invalid_state: Dictionary = valid_state.duplicate(true)
|
||||
invalid_state["display_scale"] = 1000.0
|
||||
assert(not NetworkFishShowcaseProtocol.validate_state(invalid_state))
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 5)
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 6)
|
||||
assert(NetworkProtocol.ENET_CHANNEL_COUNT == 10)
|
||||
assert(
|
||||
NetworkProtocol.FISH_QUALITY_CAPABILITY
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ func _run() -> void:
|
|||
_validate_mail_round_trip()
|
||||
_validate_collection_mastery()
|
||||
_validate_version_four_migration()
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 5)
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 6)
|
||||
assert(NetworkProtocol.ENET_CHANNEL_COUNT == 10)
|
||||
assert(NetworkProtocol.FISH_QUALITY_CAPABILITY == "fish_quality_v1")
|
||||
print("Fish quality validation: PASS")
|
||||
|
|
|
|||
|
|
@ -6,19 +6,23 @@ func _initialize() -> void:
|
|||
|
||||
|
||||
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 [
|
||||
"fur_style",
|
||||
"fur_color_2",
|
||||
"fur_color_3",
|
||||
"fur_color_4",
|
||||
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))
|
||||
assert(migrated["fur_style"] == "solid")
|
||||
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 [
|
||||
|
|
@ -38,72 +42,128 @@ func _run() -> void:
|
|||
"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_texture := CharacterCustomizationCatalog.fur_pattern_texture(
|
||||
"spots_bengal"
|
||||
)
|
||||
assert(pattern_texture != null)
|
||||
assert(pattern_texture.get_width() == 1024)
|
||||
assert(pattern_texture.get_height() == 1024)
|
||||
var arm_pattern_texture := CharacterCustomizationCatalog.fur_pattern_texture(
|
||||
"spots_bengal",
|
||||
"body_arms",
|
||||
)
|
||||
assert(arm_pattern_texture != null)
|
||||
assert(arm_pattern_texture.get_width() == 1024)
|
||||
assert(arm_pattern_texture.get_height() == 1024)
|
||||
var fox_pattern_texture := CharacterCustomizationCatalog.fur_pattern_texture(
|
||||
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",
|
||||
"body_main",
|
||||
)
|
||||
assert(fox_pattern_texture != null)
|
||||
assert(fox_pattern_texture.get_width() == 1024)
|
||||
assert(fox_pattern_texture.get_height() == 1024)
|
||||
var fox_arm_pattern_texture := (
|
||||
CharacterCustomizationCatalog.fur_pattern_texture(
|
||||
"fox", "body_arms"
|
||||
)
|
||||
)
|
||||
assert(fox_arm_pattern_texture != null)
|
||||
assert(fox_arm_pattern_texture.get_width() == 1024)
|
||||
assert(fox_arm_pattern_texture.get_height() == 1024)
|
||||
var bengal_head_texture := (
|
||||
CharacterCustomizationCatalog.fur_pattern_texture(
|
||||
"spots_bengal", "head_round"
|
||||
)
|
||||
)
|
||||
assert(bengal_head_texture != null)
|
||||
assert(bengal_head_texture.get_width() == 1024)
|
||||
assert(bengal_head_texture.get_height() == 1024)
|
||||
var fox_head_texture := CharacterCustomizationCatalog.fur_pattern_texture(
|
||||
"fox", "head_pointy"
|
||||
)
|
||||
assert(fox_head_texture != null)
|
||||
assert(fox_head_texture.get_width() == 1024)
|
||||
assert(fox_head_texture.get_height() == 1024)
|
||||
var fox_round_head_texture := (
|
||||
CharacterCustomizationCatalog.fur_pattern_texture(
|
||||
"fox", "head_round"
|
||||
)
|
||||
)
|
||||
assert(fox_round_head_texture != null)
|
||||
assert(fox_round_head_texture.get_width() == 1024)
|
||||
assert(fox_round_head_texture.get_height() == 1024)
|
||||
var fox_tail_texture := CharacterCustomizationCatalog.fur_pattern_texture(
|
||||
"fox", "tails_fox"
|
||||
)
|
||||
assert(fox_tail_texture != null)
|
||||
assert(fox_tail_texture.get_width() == 1024)
|
||||
assert(fox_tail_texture.get_height() == 1024)
|
||||
"paws",
|
||||
"stripes",
|
||||
"tiger",
|
||||
"tummy",
|
||||
])
|
||||
|
||||
var patterned := migrated.duplicate(true)
|
||||
var patterned := CharacterCustomizationCatalog.default_snapshot()
|
||||
patterned["species"] = "round"
|
||||
patterned["ears"] = "pointy_short"
|
||||
patterned["tail"] = "fox"
|
||||
patterned["fur_pattern"] = "orange"
|
||||
patterned["fur_style"] = "spots_bengal"
|
||||
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)
|
||||
|
|
@ -121,72 +181,224 @@ func _run() -> void:
|
|||
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 material := body.material_override as ShaderMaterial
|
||||
assert(material != null)
|
||||
|
||||
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("pattern_texture") == pattern_texture)
|
||||
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 arms := skeleton.get_node_or_null("body_arms") as MeshInstance3D
|
||||
assert(arms != null)
|
||||
var arm_material := arms.material_override as ShaderMaterial
|
||||
assert(arm_material != null)
|
||||
assert(
|
||||
arm_material.get_shader_parameter("pattern_texture")
|
||||
== arm_pattern_texture
|
||||
)
|
||||
var round_head := skeleton.get_node_or_null("head_round") as MeshInstance3D
|
||||
assert(round_head != null)
|
||||
var round_head_material := round_head.material_override as ShaderMaterial
|
||||
assert(round_head_material != null)
|
||||
assert(
|
||||
round_head_material.get_shader_parameter("pattern_texture")
|
||||
== bengal_head_texture
|
||||
)
|
||||
|
||||
var fox_snapshot := patterned.duplicate(true)
|
||||
fox_snapshot["species"] = "pointy"
|
||||
fox_snapshot["fur_style"] = "fox"
|
||||
fox_snapshot["tail"] = "fox"
|
||||
PlayerVisualPresenter.apply_appearance(visuals, fox_snapshot)
|
||||
var pointy_head := (
|
||||
skeleton.get_node_or_null("head_pointy") as MeshInstance3D
|
||||
)
|
||||
assert(pointy_head != null)
|
||||
var pointy_head_material := pointy_head.material_override as ShaderMaterial
|
||||
assert(pointy_head_material != null)
|
||||
assert(
|
||||
pointy_head_material.get_shader_parameter("pattern_texture")
|
||||
== fox_head_texture
|
||||
)
|
||||
fox_snapshot["species"] = "round"
|
||||
PlayerVisualPresenter.apply_appearance(visuals, fox_snapshot)
|
||||
assert(
|
||||
round_head_material.get_shader_parameter("pattern_texture")
|
||||
== fox_round_head_texture
|
||||
)
|
||||
var fox_tail := skeleton.get_node_or_null("tails_fox") as MeshInstance3D
|
||||
assert(fox_tail != null)
|
||||
var fox_tail_material := fox_tail.material_override as ShaderMaterial
|
||||
assert(fox_tail_material != null)
|
||||
assert(
|
||||
fox_tail_material.get_shader_parameter("pattern_texture")
|
||||
== fox_tail_texture
|
||||
)
|
||||
|
||||
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})
|
||||
)
|
||||
|
||||
PlayerVisualPresenter.apply_appearance(visuals, migrated)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ const FishCatalog: FishPool = preload("res://fish/pools/fish_catalog.tres")
|
|||
|
||||
|
||||
func _initialize() -> void:
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 5)
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 6)
|
||||
assert(
|
||||
NetworkWorldSpawnProtocol.CAPABILITY
|
||||
== NetworkProtocol.WORLD_SPAWN_CAPABILITY
|
||||
|
|
|
|||
|
|
@ -4,7 +4,13 @@ extends Control
|
|||
const CHECK_DEBOUNCE_SECONDS: float = 0.4
|
||||
const OPTION_GRID_COLUMNS: int = 6
|
||||
const FUR_PALETTE_GRID_COLUMNS: int = 10
|
||||
const FUR_CHANNEL_ICON_SIZE: int = 20
|
||||
const FUR_PATTERN_GRID_COLUMNS: int = 4
|
||||
const FUR_PALETTE_SWATCH_SIZE: float = 38.0
|
||||
const FUR_CHANNEL_SWATCH_SIZE: float = 22.0
|
||||
const FUR_COLOR_PICKER_POPUP_SIZE: Vector2i = Vector2i(720, 560)
|
||||
const FUR_COLOR_PICKER_SV_SIZE: Vector2i = Vector2i(520, 300)
|
||||
const FUR_SECTION_PATTERNS: String = "patterns"
|
||||
const FUR_SECTION_COLORS: String = "colors"
|
||||
const FEATURE_DRAWER_ANIMATION_SECONDS: float = 0.16
|
||||
const FEATURE_PREVIEW_SCAN_MAX_SIZE: int = 256
|
||||
const FEATURE_PREVIEW_TEXTURE_MAX_SIZE: int = 128
|
||||
|
|
@ -12,6 +18,8 @@ const FEATURE_PREVIEW_PADDING: int = 3
|
|||
const ControllerMappingManagerType = preload(
|
||||
"res://settings/controller_mapping_manager.gd"
|
||||
)
|
||||
const OrganizerTabType = preload("res://ui/components/organizer_tab.gd")
|
||||
const GameTheme: Theme = preload("res://ui/game_theme.tres")
|
||||
const AnimaleseVoiceType = preload("res://ui/animalese_voice.gd")
|
||||
const TypewriterRevealType = preload("res://ui/typewriter_reveal.gd")
|
||||
const VoiceProfilesType = preload(
|
||||
|
|
@ -56,13 +64,18 @@ var _feature_preview_cache: Dictionary = {}
|
|||
var _feature_preview_requests: Array[Dictionary] = []
|
||||
var _feature_preview_generation: int = 0
|
||||
var _feature_preview_worker_active: bool = false
|
||||
var _fur_swatch_icon_cache: Dictionary = {}
|
||||
var _expanded_feature_drawers: Dictionary = {}
|
||||
var _feature_drawer_animation_key: String = ""
|
||||
var _scale_value_label: Label
|
||||
var _voice_preview: AnimaleseVoiceType
|
||||
var _voice_preview_tween: Tween
|
||||
var _active_fur_section: String = FUR_SECTION_PATTERNS
|
||||
var _active_fur_pattern_id: String = CharacterCustomizationCatalog.FUR_STYLE_ID
|
||||
var _active_fur_color_id: String = "fur_pattern"
|
||||
var _fur_color_channel_buttons: Dictionary[String, Button] = {}
|
||||
var _fur_color_channel_swatches: Dictionary[String, Panel] = {}
|
||||
var _fur_custom_color_display: Panel
|
||||
var _fur_custom_color_label: Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -125,7 +138,6 @@ func set_world_pixel_size(pixel_size: int) -> void:
|
|||
|
||||
func activate() -> void:
|
||||
visible = true
|
||||
UtilityPageStyle.animate_in(self)
|
||||
if _service != null:
|
||||
_load_persisted()
|
||||
# Keep controller page switches on the Profile navigation bubble. Focusing
|
||||
|
|
@ -318,10 +330,17 @@ func _build_ui() -> void:
|
|||
body.alignment = BoxContainer.ALIGNMENT_BEGIN
|
||||
body.add_theme_constant_override("separation", 16)
|
||||
body_margin.add_child(body)
|
||||
var category_scroll := ScrollContainer.new()
|
||||
category_scroll.name = "CategoryScroll"
|
||||
category_scroll.custom_minimum_size.x = 120.0
|
||||
category_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
category_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
category_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO
|
||||
body.add_child(category_scroll)
|
||||
_category_list = VBoxContainer.new()
|
||||
_category_list.custom_minimum_size = Vector2(120, 0)
|
||||
_category_list.add_theme_constant_override("separation", 5)
|
||||
body.add_child(_category_list)
|
||||
category_scroll.add_child(_category_list)
|
||||
_option_list = VBoxContainer.new()
|
||||
_option_list.custom_minimum_size = Vector2(360, 0)
|
||||
_option_list.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
|
@ -402,9 +421,6 @@ func _build_ui() -> void:
|
|||
UtilityPageStyle.apply_ocean_button(_keep_editing_button)
|
||||
confirm_buttons.add_child(_keep_editing_button)
|
||||
|
||||
_voice_preview = AnimaleseVoiceType.new()
|
||||
_voice_preview.name = "ProfileVoicePreview"
|
||||
add_child(_voice_preview)
|
||||
_build_categories()
|
||||
|
||||
|
||||
|
|
@ -440,15 +456,12 @@ func _select_category(category_id: String) -> void:
|
|||
|
||||
func _refresh_options() -> void:
|
||||
_cancel_feature_preview_requests()
|
||||
_fur_color_channel_buttons.clear()
|
||||
_fur_color_channel_swatches.clear()
|
||||
_fur_custom_color_display = null
|
||||
_fur_custom_color_label = null
|
||||
for child: Node in _option_list.get_children():
|
||||
child.queue_free()
|
||||
var title := Label.new()
|
||||
title.text = _category_label(_category_id)
|
||||
title.add_theme_font_size_override("font_size", 18)
|
||||
title.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
)
|
||||
_option_list.add_child(title)
|
||||
if _category_id == VOICE_CATEGORY_ID:
|
||||
_build_voice_options()
|
||||
return
|
||||
|
|
@ -463,7 +476,7 @@ func _refresh_options() -> void:
|
|||
_option_list.add_child(empty)
|
||||
return
|
||||
if _category_id == "fur_pattern":
|
||||
_build_fur_color_options(options)
|
||||
_build_fur_options(options)
|
||||
return
|
||||
if _category_id in CharacterCustomizationCatalog.FEATURE_CATEGORIES:
|
||||
_build_feature_preview_options(options)
|
||||
|
|
@ -630,6 +643,10 @@ func _select_call_option(call_id: String) -> void:
|
|||
func _play_voice_preview() -> void:
|
||||
if _voice_preview_tween != null and _voice_preview_tween.is_valid():
|
||||
_voice_preview_tween.kill()
|
||||
if not is_instance_valid(_voice_preview):
|
||||
_voice_preview = AnimaleseVoiceType.new()
|
||||
_voice_preview.name = "ProfileVoicePreview"
|
||||
add_child(_voice_preview)
|
||||
_voice_preview_tween = _voice_preview.speak_text(
|
||||
self,
|
||||
"hello there!",
|
||||
|
|
@ -695,15 +712,6 @@ func _build_scale_option() -> void:
|
|||
_option_list.add_child(_scale_value_label)
|
||||
_update_scale_value_label(float(slider.value))
|
||||
|
||||
var gameplay_note := Label.new()
|
||||
gameplay_note.text = "Movement and collision stay the same."
|
||||
gameplay_note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
gameplay_note.add_theme_font_size_override("font_size", 12)
|
||||
gameplay_note.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
)
|
||||
_option_list.add_child(gameplay_note)
|
||||
|
||||
|
||||
func _build_feature_preview_options(_options: Array) -> void:
|
||||
var scroll := ScrollContainer.new()
|
||||
|
|
@ -984,48 +992,166 @@ func _resize_feature_preview_image(image: Image, max_dimension: int) -> void:
|
|||
)
|
||||
|
||||
|
||||
func _build_fur_color_options(options: Array) -> void:
|
||||
var pattern_label := Label.new()
|
||||
pattern_label.text = "pattern"
|
||||
pattern_label.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
func _build_fur_options(options: Array) -> void:
|
||||
var section_tabs := HBoxContainer.new()
|
||||
section_tabs.add_theme_constant_override("separation", 8)
|
||||
_option_list.add_child(section_tabs)
|
||||
for section_id: String in [FUR_SECTION_PATTERNS, FUR_SECTION_COLORS]:
|
||||
var section_button := Button.new()
|
||||
section_button.text = section_id
|
||||
section_button.toggle_mode = true
|
||||
section_button.button_pressed = _active_fur_section == section_id
|
||||
section_button.custom_minimum_size = Vector2(132.0, 34.0)
|
||||
section_button.pressed.connect(_select_fur_section.bind(section_id))
|
||||
UtilityPageStyle.apply_compact_ocean_button(section_button)
|
||||
section_tabs.add_child(section_button)
|
||||
|
||||
if _active_fur_section == FUR_SECTION_COLORS:
|
||||
_build_fur_color_channels(options)
|
||||
return
|
||||
_build_fur_pattern_options()
|
||||
|
||||
|
||||
func _build_fur_pattern_options() -> void:
|
||||
if _active_fur_pattern_id not in CharacterCustomizationCatalog.FUR_STYLE_IDS:
|
||||
_active_fur_pattern_id = CharacterCustomizationCatalog.FUR_STYLE_ID
|
||||
var pattern_stack := VBoxContainer.new()
|
||||
pattern_stack.name = "FurPatternStack"
|
||||
pattern_stack.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
pattern_stack.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
pattern_stack.add_theme_constant_override("separation", -10)
|
||||
_option_list.add_child(pattern_stack)
|
||||
var part_tab_margin := MarginContainer.new()
|
||||
part_tab_margin.name = "FurPatternPartTabMargin"
|
||||
part_tab_margin.custom_minimum_size.y = 38.0
|
||||
part_tab_margin.add_theme_constant_override("margin_left", 14)
|
||||
pattern_stack.add_child(part_tab_margin)
|
||||
var part_tabs := HBoxContainer.new()
|
||||
part_tabs.name = "FurPatternPartTabs"
|
||||
part_tabs.custom_minimum_size.y = 38.0
|
||||
part_tabs.add_theme_constant_override("separation", 4)
|
||||
part_tab_margin.add_child(part_tabs)
|
||||
for style_index: int in range(
|
||||
CharacterCustomizationCatalog.FUR_STYLE_IDS.size()
|
||||
):
|
||||
var style_field: String = (
|
||||
CharacterCustomizationCatalog.FUR_STYLE_IDS[style_index]
|
||||
)
|
||||
_option_list.add_child(pattern_label)
|
||||
var pattern_row := HBoxContainer.new()
|
||||
pattern_row.add_theme_constant_override("separation", 8)
|
||||
_option_list.add_child(pattern_row)
|
||||
var part_tab: OrganizerTab = OrganizerTabType.new()
|
||||
part_tab.text = CharacterCustomizationCatalog.fur_style_label(
|
||||
style_field
|
||||
)
|
||||
part_tab.palette_index = mini(style_index, 2)
|
||||
part_tab.custom_minimum_size = Vector2(90.0, 38.0)
|
||||
part_tab.focus_mode = Control.FOCUS_ALL
|
||||
part_tab.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
part_tab.pressed.connect(_select_fur_pattern_part.bind(style_field))
|
||||
part_tabs.add_child(part_tab)
|
||||
part_tab.set_selected(
|
||||
_active_fur_pattern_id == style_field,
|
||||
false,
|
||||
)
|
||||
|
||||
var pattern_panel := PanelContainer.new()
|
||||
pattern_panel.name = "FurPatternOptionsPanel"
|
||||
pattern_panel.custom_minimum_size.y = 180.0
|
||||
pattern_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
pattern_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
pattern_panel.add_theme_stylebox_override(
|
||||
"panel",
|
||||
UtilityPageStyle.rounded_style(UtilityPageStyle.OCEAN_FIELD, 12),
|
||||
)
|
||||
pattern_stack.add_child(pattern_panel)
|
||||
var pattern_margin := MarginContainer.new()
|
||||
pattern_margin.add_theme_constant_override("margin_left", 12)
|
||||
pattern_margin.add_theme_constant_override("margin_top", 10)
|
||||
pattern_margin.add_theme_constant_override("margin_right", 12)
|
||||
pattern_margin.add_theme_constant_override("margin_bottom", 10)
|
||||
pattern_panel.add_child(pattern_margin)
|
||||
var pattern_scroll := ScrollContainer.new()
|
||||
pattern_scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
pattern_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
pattern_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
pattern_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO
|
||||
pattern_margin.add_child(pattern_scroll)
|
||||
var pattern_grid := GridContainer.new()
|
||||
pattern_grid.columns = FUR_PATTERN_GRID_COLUMNS
|
||||
pattern_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
pattern_grid.add_theme_constant_override("h_separation", 8)
|
||||
pattern_grid.add_theme_constant_override("v_separation", 8)
|
||||
pattern_scroll.add_child(pattern_grid)
|
||||
var selected_style_id := str(_draft_appearance.get(
|
||||
CharacterCustomizationCatalog.FUR_STYLE_ID,
|
||||
_active_fur_pattern_id,
|
||||
CharacterCustomizationCatalog.DEFAULT_FUR_STYLE,
|
||||
))
|
||||
for pattern_option: Dictionary in CharacterCustomizationCatalog.options_for(
|
||||
CharacterCustomizationCatalog.FUR_STYLE_ID
|
||||
):
|
||||
var pattern_options := (
|
||||
CharacterCustomizationCatalog.fur_pattern_options_for_field(
|
||||
_active_fur_pattern_id,
|
||||
_draft_appearance,
|
||||
)
|
||||
)
|
||||
var selected_style_available := false
|
||||
for pattern_option: Dictionary in pattern_options:
|
||||
if str(pattern_option.get("id", "")) == selected_style_id:
|
||||
selected_style_available = true
|
||||
break
|
||||
var displayed_style_id := (
|
||||
selected_style_id
|
||||
if selected_style_available
|
||||
else CharacterCustomizationCatalog.DEFAULT_FUR_STYLE
|
||||
)
|
||||
for pattern_option: Dictionary in pattern_options:
|
||||
var pattern_id := str(pattern_option.get("id", ""))
|
||||
var pattern_button := Button.new()
|
||||
pattern_button.text = str(pattern_option.get("label", pattern_id))
|
||||
pattern_button.toggle_mode = true
|
||||
pattern_button.button_pressed = selected_style_id == pattern_id
|
||||
pattern_button.button_pressed = displayed_style_id == pattern_id
|
||||
pattern_button.custom_minimum_size = Vector2(118.0, 32.0)
|
||||
pattern_button.pressed.connect(_select_option.bind(
|
||||
CharacterCustomizationCatalog.FUR_STYLE_ID,
|
||||
_active_fur_pattern_id,
|
||||
pattern_id,
|
||||
))
|
||||
UtilityPageStyle.apply_compact_ocean_button(pattern_button)
|
||||
pattern_row.add_child(pattern_button)
|
||||
pattern_grid.add_child(pattern_button)
|
||||
|
||||
var channel_label := Label.new()
|
||||
channel_label.text = "color channel"
|
||||
channel_label.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
|
||||
func _build_fur_color_channels(options: Array) -> void:
|
||||
var color_panel := PanelContainer.new()
|
||||
color_panel.name = "FurColorOptionsPanel"
|
||||
color_panel.custom_minimum_size.y = 250.0
|
||||
color_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
color_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
color_panel.add_theme_stylebox_override(
|
||||
"panel",
|
||||
UtilityPageStyle.rounded_style(UtilityPageStyle.OCEAN_FIELD, 12),
|
||||
)
|
||||
_option_list.add_child(channel_label)
|
||||
_option_list.add_child(color_panel)
|
||||
var color_margin := MarginContainer.new()
|
||||
color_margin.add_theme_constant_override("margin_left", 14)
|
||||
color_margin.add_theme_constant_override("margin_top", 12)
|
||||
color_margin.add_theme_constant_override("margin_right", 14)
|
||||
color_margin.add_theme_constant_override("margin_bottom", 12)
|
||||
color_panel.add_child(color_margin)
|
||||
var color_stack := VBoxContainer.new()
|
||||
color_stack.name = "FurColorStack"
|
||||
color_stack.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
color_stack.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
color_stack.add_theme_constant_override("separation", 10)
|
||||
color_margin.add_child(color_stack)
|
||||
|
||||
var channel_grid := GridContainer.new()
|
||||
channel_grid.columns = 2
|
||||
channel_grid.add_theme_constant_override("h_separation", 8)
|
||||
channel_grid.name = "FurColorChannelGrid"
|
||||
channel_grid.columns = CharacterCustomizationCatalog.FUR_COLOR_IDS.size()
|
||||
channel_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
channel_grid.add_theme_constant_override("h_separation", 10)
|
||||
channel_grid.add_theme_constant_override("v_separation", 8)
|
||||
_option_list.add_child(channel_grid)
|
||||
for color_category_id: String in CharacterCustomizationCatalog.FUR_COLOR_IDS:
|
||||
color_stack.add_child(channel_grid)
|
||||
for color_index: int in range(
|
||||
CharacterCustomizationCatalog.FUR_COLOR_IDS.size()
|
||||
):
|
||||
var color_category_id: String = (
|
||||
CharacterCustomizationCatalog.FUR_COLOR_IDS[color_index]
|
||||
)
|
||||
var selected_color_id := str(_draft_appearance.get(
|
||||
color_category_id,
|
||||
"white",
|
||||
|
|
@ -1035,22 +1161,16 @@ func _build_fur_color_options(options: Array) -> void:
|
|||
selected_color_id,
|
||||
)
|
||||
var channel_button := Button.new()
|
||||
channel_button.text = CharacterCustomizationCatalog.fur_color_label(
|
||||
color_category_id
|
||||
)
|
||||
channel_button.icon = _fur_swatch_icon(selected_color)
|
||||
channel_button.icon_alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
channel_button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
channel_button.add_theme_constant_override(
|
||||
"icon_max_width", FUR_CHANNEL_ICON_SIZE
|
||||
)
|
||||
channel_button.add_theme_constant_override("h_separation", 8)
|
||||
channel_button.name = "FurColorSlot%d" % (color_index + 1)
|
||||
channel_button.text = ""
|
||||
channel_button.accessibility_name = "fur color %d" % (color_index + 1)
|
||||
channel_button.tooltip_text = "Edit %s: %s" % [
|
||||
CharacterCustomizationCatalog.fur_color_label(color_category_id),
|
||||
_fur_option_label(options, selected_color_id),
|
||||
]
|
||||
channel_button.toggle_mode = true
|
||||
channel_button.custom_minimum_size = Vector2(140.0, 34.0)
|
||||
channel_button.custom_minimum_size = Vector2(104.0, 42.0)
|
||||
channel_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
channel_button.button_pressed = (
|
||||
_active_fur_color_id == color_category_id
|
||||
)
|
||||
|
|
@ -1059,30 +1179,61 @@ func _build_fur_color_options(options: Array) -> void:
|
|||
)
|
||||
UtilityPageStyle.apply_compact_ocean_button(channel_button)
|
||||
channel_grid.add_child(channel_button)
|
||||
var channel_contents := HBoxContainer.new()
|
||||
channel_contents.name = "ChannelContents"
|
||||
channel_contents.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
channel_contents.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
channel_contents.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
channel_contents.add_theme_constant_override("separation", 8)
|
||||
channel_button.add_child(channel_contents)
|
||||
var channel_label := Label.new()
|
||||
channel_label.name = "ChannelNumber"
|
||||
channel_label.text = str(color_index + 1)
|
||||
channel_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
channel_label.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
channel_label.add_theme_font_override(
|
||||
"font", UtilityPageStyle.TuffyFont
|
||||
)
|
||||
channel_label.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
)
|
||||
channel_contents.add_child(channel_label)
|
||||
var channel_swatch := Panel.new()
|
||||
channel_swatch.name = "ActiveColorSwatch"
|
||||
channel_swatch.custom_minimum_size = (
|
||||
Vector2.ONE * FUR_CHANNEL_SWATCH_SIZE
|
||||
)
|
||||
channel_swatch.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
channel_swatch.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
channel_swatch.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_set_fur_color_swatch(channel_swatch, selected_color)
|
||||
channel_contents.add_child(channel_swatch)
|
||||
_fur_color_channel_buttons[color_category_id] = channel_button
|
||||
_fur_color_channel_swatches[color_category_id] = channel_swatch
|
||||
|
||||
_build_fur_palette(_active_fur_color_id, options)
|
||||
_build_fur_palette(_active_fur_color_id, options, color_stack)
|
||||
|
||||
|
||||
func _build_fur_palette(category_id: String, options: Array) -> void:
|
||||
func _build_fur_palette(
|
||||
category_id: String,
|
||||
options: Array,
|
||||
parent: VBoxContainer,
|
||||
) -> void:
|
||||
var selected_id: String = CharacterCustomizationCatalog.canonical_option_id(
|
||||
category_id,
|
||||
str(_draft_appearance.get(category_id, "white")),
|
||||
)
|
||||
var label := Label.new()
|
||||
label.text = "%s palette" % CharacterCustomizationCatalog.fur_color_label(
|
||||
category_id
|
||||
)
|
||||
label.add_theme_font_size_override("font_size", 14)
|
||||
label.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
)
|
||||
_option_list.add_child(label)
|
||||
|
||||
var palette_center := CenterContainer.new()
|
||||
palette_center.name = "FurPaletteCenter"
|
||||
palette_center.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
palette_center.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
parent.add_child(palette_center)
|
||||
var grid := GridContainer.new()
|
||||
grid.name = "FurPaletteGrid"
|
||||
grid.columns = FUR_PALETTE_GRID_COLUMNS
|
||||
grid.add_theme_constant_override("h_separation", 6)
|
||||
grid.add_theme_constant_override("v_separation", 6)
|
||||
_option_list.add_child(grid)
|
||||
grid.add_theme_constant_override("h_separation", 8)
|
||||
grid.add_theme_constant_override("v_separation", 8)
|
||||
palette_center.add_child(grid)
|
||||
for option: Dictionary in options:
|
||||
var option_id: String = str(option.get("id", ""))
|
||||
var color: Color = CharacterCustomizationCatalog.option_color(
|
||||
|
|
@ -1095,50 +1246,73 @@ func _build_fur_palette(category_id: String, options: Array) -> void:
|
|||
str(option.get("label", option_id)),
|
||||
]
|
||||
button.toggle_mode = true
|
||||
button.custom_minimum_size = Vector2(30.0, 30.0)
|
||||
button.custom_minimum_size = (
|
||||
Vector2.ONE * FUR_PALETTE_SWATCH_SIZE
|
||||
)
|
||||
button.button_pressed = selected_id == option_id
|
||||
button.pressed.connect(_select_option.bind(category_id, option_id))
|
||||
_apply_fur_swatch_style(button, color, button.button_pressed)
|
||||
grid.add_child(button)
|
||||
|
||||
var selected_color := CharacterCustomizationCatalog.option_color(
|
||||
category_id,
|
||||
selected_id,
|
||||
)
|
||||
var custom_picker := ColorPickerButton.new()
|
||||
custom_picker.name = "FurCustomColorPicker"
|
||||
custom_picker.text = ""
|
||||
custom_picker.accessibility_name = "custom color"
|
||||
custom_picker.color = selected_color
|
||||
custom_picker.edit_alpha = false
|
||||
custom_picker.edit_intensity = false
|
||||
custom_picker.custom_minimum_size = Vector2(0.0, 44.0)
|
||||
custom_picker.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
custom_picker.tooltip_text = "Choose a custom color for channel %d." % (
|
||||
CharacterCustomizationCatalog.FUR_COLOR_IDS.find(category_id) + 1
|
||||
)
|
||||
custom_picker.color_changed.connect(
|
||||
_select_custom_fur_color.bind(category_id)
|
||||
)
|
||||
custom_picker.popup_closed.connect(_refresh_options)
|
||||
UtilityPageStyle.apply_compact_ocean_button(custom_picker)
|
||||
_round_fur_color_picker_button(custom_picker)
|
||||
_theme_fur_color_picker_popup(custom_picker)
|
||||
parent.add_child(custom_picker)
|
||||
var picker_display := Panel.new()
|
||||
picker_display.name = "FurCustomColorDisplay"
|
||||
picker_display.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
picker_display.offset_left = 4.0
|
||||
picker_display.offset_top = 4.0
|
||||
picker_display.offset_right = -4.0
|
||||
picker_display.offset_bottom = -4.0
|
||||
picker_display.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
picker_display.z_index = 1
|
||||
custom_picker.add_child(picker_display)
|
||||
var picker_label := Label.new()
|
||||
picker_label.name = "FurCustomColorLabel"
|
||||
picker_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
picker_label.text = "custom color"
|
||||
picker_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
picker_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
picker_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
picker_label.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
||||
picker_display.add_child(picker_label)
|
||||
_fur_custom_color_display = picker_display
|
||||
_fur_custom_color_label = picker_label
|
||||
_update_fur_custom_color_display(selected_color)
|
||||
|
||||
|
||||
func _fur_option_label(options: Array, option_id: String) -> String:
|
||||
if CharacterCustomizationCatalog.is_custom_fur_color_id(option_id):
|
||||
return "custom #%s" % option_id.trim_prefix(
|
||||
CharacterCustomizationCatalog.CUSTOM_FUR_COLOR_PREFIX
|
||||
)
|
||||
for option: Dictionary in options:
|
||||
if str(option.get("id", "")) == option_id:
|
||||
return str(option.get("label", option_id))
|
||||
return option_id
|
||||
|
||||
|
||||
func _fur_swatch_icon(color: Color) -> Texture2D:
|
||||
var cache_id := color.to_html(true)
|
||||
if _fur_swatch_icon_cache.has(cache_id):
|
||||
return _fur_swatch_icon_cache[cache_id] as Texture2D
|
||||
var image := Image.create(
|
||||
FUR_CHANNEL_ICON_SIZE,
|
||||
FUR_CHANNEL_ICON_SIZE,
|
||||
false,
|
||||
Image.FORMAT_RGBA8,
|
||||
)
|
||||
var center := Vector2.ONE * (float(FUR_CHANNEL_ICON_SIZE) * 0.5)
|
||||
var radius := float(FUR_CHANNEL_ICON_SIZE) * 0.39
|
||||
for y: int in range(FUR_CHANNEL_ICON_SIZE):
|
||||
for x: int in range(FUR_CHANNEL_ICON_SIZE):
|
||||
var sample_position := Vector2(float(x) + 0.5, float(y) + 0.5)
|
||||
var alpha := clampf(
|
||||
radius + 0.8 - sample_position.distance_to(center),
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
image.set_pixel(
|
||||
x,
|
||||
y,
|
||||
Color(color.r, color.g, color.b, color.a * alpha),
|
||||
)
|
||||
var texture := ImageTexture.create_from_image(image)
|
||||
_fur_swatch_icon_cache[cache_id] = texture
|
||||
return texture
|
||||
|
||||
|
||||
func _select_fur_color_slot(category_id: String) -> void:
|
||||
if category_id not in CharacterCustomizationCatalog.FUR_COLOR_IDS:
|
||||
return
|
||||
|
|
@ -1146,6 +1320,38 @@ func _select_fur_color_slot(category_id: String) -> void:
|
|||
_refresh_options()
|
||||
|
||||
|
||||
func _select_fur_section(section_id: String) -> void:
|
||||
if section_id not in [FUR_SECTION_PATTERNS, FUR_SECTION_COLORS]:
|
||||
return
|
||||
_active_fur_section = section_id
|
||||
_refresh_options()
|
||||
|
||||
|
||||
func _select_custom_fur_color(color: Color, category_id: String) -> void:
|
||||
if category_id not in CharacterCustomizationCatalog.FUR_COLOR_IDS:
|
||||
return
|
||||
var option_id := CharacterCustomizationCatalog.custom_fur_color_id(color)
|
||||
_draft_appearance[category_id] = option_id
|
||||
_update_fur_channel_color(
|
||||
category_id,
|
||||
color,
|
||||
"custom #%s" % option_id.trim_prefix(
|
||||
CharacterCustomizationCatalog.CUSTOM_FUR_COLOR_PREFIX
|
||||
),
|
||||
)
|
||||
_update_fur_custom_color_display(color)
|
||||
_preview.apply_appearance_profile(_draft_appearance)
|
||||
_dirty = _draft_differs()
|
||||
_refresh_actions()
|
||||
|
||||
|
||||
func _select_fur_pattern_part(category_id: String) -> void:
|
||||
if category_id not in CharacterCustomizationCatalog.FUR_STYLE_IDS:
|
||||
return
|
||||
_active_fur_pattern_id = category_id
|
||||
_refresh_options()
|
||||
|
||||
|
||||
func _apply_fur_swatch_style(
|
||||
button: Button,
|
||||
color: Color,
|
||||
|
|
@ -1164,6 +1370,98 @@ func _apply_fur_swatch_style(
|
|||
button.add_theme_stylebox_override("focus", selected_style)
|
||||
|
||||
|
||||
func _set_fur_color_swatch(swatch: Panel, color: Color) -> void:
|
||||
var swatch_style := UtilityPageStyle.rounded_style(
|
||||
color,
|
||||
roundi(FUR_CHANNEL_SWATCH_SIZE * 0.5),
|
||||
)
|
||||
swatch_style.anti_aliasing = true
|
||||
swatch.add_theme_stylebox_override(
|
||||
"panel", swatch_style
|
||||
)
|
||||
|
||||
|
||||
func _update_fur_channel_color(
|
||||
category_id: String,
|
||||
color: Color,
|
||||
option_label: String,
|
||||
) -> void:
|
||||
var swatch: Panel = _fur_color_channel_swatches.get(category_id)
|
||||
if swatch != null and is_instance_valid(swatch):
|
||||
_set_fur_color_swatch(swatch, color)
|
||||
var button: Button = _fur_color_channel_buttons.get(category_id)
|
||||
if button != null and is_instance_valid(button):
|
||||
button.tooltip_text = "Edit %s: %s" % [
|
||||
CharacterCustomizationCatalog.fur_color_label(category_id),
|
||||
option_label,
|
||||
]
|
||||
|
||||
|
||||
func _round_fur_color_picker_button(button: ColorPickerButton) -> void:
|
||||
for state: StringName in [
|
||||
&"normal", &"hover", &"pressed", &"focus", &"disabled",
|
||||
]:
|
||||
var style := button.get_theme_stylebox(state).duplicate() as StyleBoxFlat
|
||||
if style == null:
|
||||
continue
|
||||
style.set_corner_radius_all(14)
|
||||
style.content_margin_left = 14.0
|
||||
style.content_margin_top = 14.0
|
||||
style.content_margin_right = 14.0
|
||||
style.content_margin_bottom = 14.0
|
||||
button.add_theme_stylebox_override(state, style)
|
||||
|
||||
|
||||
func _theme_fur_color_picker_popup(button: ColorPickerButton) -> void:
|
||||
var popup: PopupPanel = button.get_popup()
|
||||
var picker: ColorPicker = button.get_picker()
|
||||
popup.accessibility_name = "custom fur color picker"
|
||||
popup.theme = GameTheme
|
||||
popup.min_size = FUR_COLOR_PICKER_POPUP_SIZE
|
||||
popup.size = FUR_COLOR_PICKER_POPUP_SIZE
|
||||
var popup_style := UtilityPageStyle.rounded_style(
|
||||
UtilityPageStyle.OCEAN_PANEL_DEEP,
|
||||
20,
|
||||
)
|
||||
popup_style.content_margin_left = 24.0
|
||||
popup_style.content_margin_top = 24.0
|
||||
popup_style.content_margin_right = 24.0
|
||||
popup_style.content_margin_bottom = 24.0
|
||||
popup.add_theme_stylebox_override("panel", popup_style)
|
||||
picker.custom_minimum_size = Vector2(
|
||||
FUR_COLOR_PICKER_POPUP_SIZE.x - 48,
|
||||
FUR_COLOR_PICKER_POPUP_SIZE.y - 48,
|
||||
)
|
||||
picker.edit_alpha = false
|
||||
picker.edit_intensity = false
|
||||
picker.can_add_swatches = false
|
||||
picker.presets_visible = false
|
||||
picker.add_theme_constant_override("margin", 16)
|
||||
picker.add_theme_constant_override("sv_width", FUR_COLOR_PICKER_SV_SIZE.x)
|
||||
picker.add_theme_constant_override("sv_height", FUR_COLOR_PICKER_SV_SIZE.y)
|
||||
picker.add_theme_constant_override("h_width", 38)
|
||||
picker.add_theme_constant_override("label_width", 28)
|
||||
|
||||
|
||||
func _update_fur_custom_color_display(color: Color) -> void:
|
||||
if (
|
||||
_fur_custom_color_display == null
|
||||
or not is_instance_valid(_fur_custom_color_display)
|
||||
or _fur_custom_color_label == null
|
||||
or not is_instance_valid(_fur_custom_color_label)
|
||||
):
|
||||
return
|
||||
_fur_custom_color_display.add_theme_stylebox_override(
|
||||
"panel", UtilityPageStyle.rounded_style(color, 11)
|
||||
)
|
||||
_fur_custom_color_label.add_theme_color_override(
|
||||
"font_color",
|
||||
UtilityPageStyle.NAVY
|
||||
if color.get_luminance() >= 0.52
|
||||
else UtilityPageStyle.OCEAN_TEXT_PRIMARY,
|
||||
)
|
||||
|
||||
|
||||
func _select_option(category_id: String, option_id: String) -> void:
|
||||
_draft_appearance[category_id] = option_id
|
||||
_preview.apply_appearance_profile(_draft_appearance)
|
||||
|
|
|
|||