Character Appearance Phase 1 Done
Bump appearance payloads to version 2 and add `morphRegions` and `facialBoneMorphs` capture, serialization, and proxy application. Also fix head-part syncing when sender and proxy counts differ by reallocating the proxy array instead of deferring the whole list, and update the Python harness and protocol docs to match.
This commit is contained in:
+38
-2
@@ -99,6 +99,8 @@ def get_optional_appearance(packet: dict[str, Any]) -> dict[str, Any] | None:
|
||||
"facialHairColorFormId": value.get("facialHairColorFormId", ""),
|
||||
"headParts": [],
|
||||
"morphs": [],
|
||||
"morphRegions": [],
|
||||
"facialBoneMorphs": [],
|
||||
}
|
||||
|
||||
head_parts = value.get("headParts", [])
|
||||
@@ -113,9 +115,41 @@ def get_optional_appearance(packet: dict[str, Any]) -> dict[str, Any] | None:
|
||||
if isinstance(morph, dict) and isinstance(morph.get("id", ""), str)
|
||||
]
|
||||
|
||||
# Added in appearance version 2; older senders omit these and the lists stay empty.
|
||||
morph_regions = value.get("morphRegions", [])
|
||||
if isinstance(morph_regions, list):
|
||||
appearance["morphRegions"] = [
|
||||
get_optional_float({"v": entry}, "v")
|
||||
for entry in morph_regions
|
||||
if isinstance(entry, (int, float))
|
||||
]
|
||||
|
||||
facial_bone_morphs = value.get("facialBoneMorphs", [])
|
||||
if isinstance(facial_bone_morphs, list):
|
||||
appearance["facialBoneMorphs"] = [
|
||||
{
|
||||
"id": morph.get("id", ""),
|
||||
"position": _read_float3(morph.get("position")),
|
||||
"rotation": _read_float3(morph.get("rotation")),
|
||||
"scale": _read_float3(morph.get("scale"), default=1.0),
|
||||
}
|
||||
for morph in facial_bone_morphs
|
||||
if isinstance(morph, dict) and isinstance(morph.get("id", ""), str)
|
||||
]
|
||||
|
||||
return appearance
|
||||
|
||||
|
||||
def _read_float3(value: Any, default: float = 0.0) -> list[float]:
|
||||
result = [default, default, default]
|
||||
if isinstance(value, list):
|
||||
for index in range(min(len(value), 3)):
|
||||
element = value[index]
|
||||
if isinstance(element, (int, float)):
|
||||
result[index] = float(element)
|
||||
return result
|
||||
|
||||
|
||||
def update_host_world_state(packet: dict[str, Any]) -> None:
|
||||
global host_world_state
|
||||
|
||||
@@ -238,7 +272,9 @@ def print_remote_player(player: dict[str, Any]) -> None:
|
||||
(
|
||||
f"version={appearance.get('version')}, race={appearance.get('raceFormId') or '<empty>'}, "
|
||||
f"height={appearance.get('height')}, headParts={len(appearance.get('headParts', []))}, "
|
||||
f"morphs={len(appearance.get('morphs', []))}"
|
||||
f"morphs={len(appearance.get('morphs', []))}, "
|
||||
f"morphRegions={len(appearance.get('morphRegions', []))}, "
|
||||
f"facialBoneMorphs={len(appearance.get('facialBoneMorphs', []))}"
|
||||
)
|
||||
if isinstance(appearance, dict)
|
||||
else "<not sent>"
|
||||
@@ -284,7 +320,7 @@ def print_remote_players() -> None:
|
||||
)
|
||||
appearance = player.get("appearance")
|
||||
appearance_text = (
|
||||
f"race={appearance.get('raceFormId') or '<empty>'}, headParts={len(appearance.get('headParts', []))}, morphs={len(appearance.get('morphs', []))}"
|
||||
f"race={appearance.get('raceFormId') or '<empty>'}, headParts={len(appearance.get('headParts', []))}, morphs={len(appearance.get('morphs', []))}, morphRegions={len(appearance.get('morphRegions', []))}, facialBoneMorphs={len(appearance.get('facialBoneMorphs', []))}"
|
||||
if isinstance(appearance, dict)
|
||||
else "<not sent>"
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ IDLE_ANGLE_Z = 3.93
|
||||
TELEPORT_TEST_OFFSET_X = 900.0
|
||||
TELEPORT_TEST_OFFSET_Y = 450.0
|
||||
DEFAULT_APPEARANCE = {
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"raceFormId": "00013746",
|
||||
"height": 1.0,
|
||||
"morphWeight": {"thin": 0.0, "muscular": 0.0, "large": 0.0},
|
||||
@@ -53,6 +53,8 @@ DEFAULT_APPEARANCE = {
|
||||
"facialHairColorFormId": "",
|
||||
"headParts": [],
|
||||
"morphs": [],
|
||||
"morphRegions": [],
|
||||
"facialBoneMorphs": [],
|
||||
}
|
||||
|
||||
ClientSnapshotProvider = Callable[[], list[dict[str, Any]]]
|
||||
|
||||
Reference in New Issue
Block a user