Start handling AvatarAppearance messages
This commit is contained in:
@@ -267,7 +267,7 @@ class Message:
|
||||
block.message_name = self.name
|
||||
block.finalize()
|
||||
|
||||
def get_block(self, block_name: str, default=None, /) -> Optional[Block]:
|
||||
def get_blocks(self, block_name: str, default=None, /) -> Optional[MsgBlockList]:
|
||||
return self.blocks.get(block_name, default)
|
||||
|
||||
@property
|
||||
|
||||
@@ -16,6 +16,8 @@ from hippolyzer.lib.base.datatypes import UUID
|
||||
from hippolyzer.lib.base.helpers import get_resource_filename
|
||||
from hippolyzer.lib.base.inventory import InventorySaleInfo, InventoryPermissions
|
||||
from hippolyzer.lib.base.legacy_schema import SchemaBase, parse_schema_line, SchemaParsingError
|
||||
import hippolyzer.lib.base.serialization as se
|
||||
from hippolyzer.lib.base.message.message import Message
|
||||
from hippolyzer.lib.base.templates import WearableType
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -78,6 +80,13 @@ class AvatarTEIndex(enum.IntEnum):
|
||||
return self.name.endswith("_BAKED")
|
||||
|
||||
|
||||
class VisualParamGroup(enum.IntEnum):
|
||||
TWEAKABLE = 0
|
||||
ANIMATABLE = 1
|
||||
TWEAKABLE_NO_TRANSMIT = 2
|
||||
TRANSMIT_NOT_TWEAKABLE = 3
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class VisualParam:
|
||||
id: int
|
||||
@@ -85,26 +94,47 @@ class VisualParam:
|
||||
value_min: float
|
||||
value_max: float
|
||||
value_default: float
|
||||
group: VisualParamGroup
|
||||
# These might be `None` if the param isn't meant to be directly edited
|
||||
edit_group: Optional[str]
|
||||
wearable: Optional[str]
|
||||
|
||||
def dequantize_val(self, val: int) -> float:
|
||||
"""Dequantize U8 values from AvatarAppearance messages"""
|
||||
spec = se.QuantizedFloat(se.U8, self.value_min, self.value_max, False)
|
||||
return spec.decode(val, None)
|
||||
|
||||
|
||||
class VisualParams(List[VisualParam]):
|
||||
def __init__(self, lad_path):
|
||||
super().__init__()
|
||||
with open(lad_path, "rb") as f:
|
||||
doc = parse_etree(f)
|
||||
|
||||
temp_params = []
|
||||
for param in doc.findall(".//param"):
|
||||
self.append(VisualParam(
|
||||
temp_params.append(VisualParam(
|
||||
id=int(param.attrib["id"]),
|
||||
name=param.attrib["name"],
|
||||
group=VisualParamGroup(int(param.get("group", "0"))),
|
||||
edit_group=param.get("edit_group"),
|
||||
wearable=param.get("wearable"),
|
||||
value_min=float(param.attrib["value_min"]),
|
||||
value_max=float(param.attrib["value_max"]),
|
||||
value_default=float(param.attrib.get("value_default", 0.0))
|
||||
))
|
||||
# Some functionality relies on the list being sorted by ID, though there may be holes.
|
||||
temp_params.sort(key=lambda x: x.id)
|
||||
# Remove dupes, only using the last value present (matching indra behavior)
|
||||
# This is necessary to remove the duplicate eye pop entry...
|
||||
self.extend({x.id: x for x in temp_params}.values())
|
||||
|
||||
@property
|
||||
def appearance_params(self) -> Iterator[VisualParam]:
|
||||
for param in self:
|
||||
if param.group not in (VisualParamGroup.TWEAKABLE, VisualParamGroup.TRANSMIT_NOT_TWEAKABLE):
|
||||
continue
|
||||
yield param
|
||||
|
||||
def by_name(self, name: str) -> VisualParam:
|
||||
return [x for x in self if x.name == name][0]
|
||||
@@ -118,6 +148,12 @@ class VisualParams(List[VisualParam]):
|
||||
def by_id(self, vparam_id: int) -> VisualParam:
|
||||
return [x for x in self if x.id == vparam_id][0]
|
||||
|
||||
def parse_appearance_message(self, message: Message) -> Dict[int, float]:
|
||||
params = {}
|
||||
for param, value_block in zip(self.appearance_params, message["VisualParam"]):
|
||||
params[param.id] = param.dequantize_val(value_block["ParamValue"])
|
||||
return params
|
||||
|
||||
|
||||
VISUAL_PARAMS = VisualParams(get_resource_filename("lib/base/data/avatar_lad.xml"))
|
||||
|
||||
|
||||
@@ -378,7 +378,7 @@ class HippoClientSession(BaseClientSession):
|
||||
sim_seed = msg["EventData"]["seed-capability"]
|
||||
# We teleported or cross region, opening comms to new sim
|
||||
elif msg.name in ("TeleportFinish", "CrossedRegion"):
|
||||
sim_block = msg.get_block("RegionData", msg.get_block("Info"))[0]
|
||||
sim_block = msg.get_blocks("RegionData", msg.get_blocks("Info"))[0]
|
||||
sim_addr = (sim_block["SimIP"], sim_block["SimPort"])
|
||||
sim_handle = sim_block["RegionHandle"]
|
||||
sim_seed = sim_block["SeedCapability"]
|
||||
|
||||
@@ -27,6 +27,7 @@ from hippolyzer.lib.base.objects import (
|
||||
Object, handle_to_global_pos,
|
||||
)
|
||||
from hippolyzer.lib.base.settings import Settings
|
||||
from hippolyzer.lib.base.wearables import VISUAL_PARAMS
|
||||
from hippolyzer.lib.client.namecache import NameCache, NameCacheEntry
|
||||
from hippolyzer.lib.base.templates import PCode, ObjectStateSerializer, XferFilePath
|
||||
from hippolyzer.lib.base import llsd
|
||||
@@ -47,6 +48,7 @@ class ObjectUpdateType(enum.IntEnum):
|
||||
COSTS = enum.auto()
|
||||
KILL = enum.auto()
|
||||
ANIMATIONS = enum.auto()
|
||||
APPEARANCE = enum.auto()
|
||||
|
||||
|
||||
class ClientObjectManager:
|
||||
@@ -299,6 +301,8 @@ class ClientWorldObjectManager:
|
||||
self._handle_animation_message)
|
||||
message_handler.subscribe("ObjectAnimation",
|
||||
self._handle_animation_message)
|
||||
message_handler.subscribe("AvatarAppearance",
|
||||
self._handle_avatar_appearance_message)
|
||||
|
||||
def lookup_fullid(self, full_id: UUID) -> Optional[Object]:
|
||||
return self._fullid_lookup.get(full_id, None)
|
||||
@@ -663,7 +667,8 @@ class ClientWorldObjectManager:
|
||||
elif message.name == "ObjectAnimation":
|
||||
obj = self.lookup_fullid(sender_id)
|
||||
if not obj:
|
||||
LOG.warning(f"Received AvatarAnimation for avatar with no object {sender_id}")
|
||||
# This is only a debug message in the viewer, but let's be louder.
|
||||
LOG.warning(f"Received ObjectAnimation for animesh with no object {sender_id}")
|
||||
return
|
||||
else:
|
||||
LOG.error(f"Unknown animation message type: {message.name}")
|
||||
@@ -674,6 +679,31 @@ class ClientWorldObjectManager:
|
||||
obj.Animations.append(block["AnimID"])
|
||||
self._run_object_update_hooks(obj, {"Animations"}, ObjectUpdateType.ANIMATIONS, message)
|
||||
|
||||
def _handle_avatar_appearance_message(self, message: Message):
|
||||
sender_id: UUID = message["Sender"]["ID"]
|
||||
if message["Sender"]["IsTrial"]:
|
||||
return
|
||||
av = self.lookup_avatar(sender_id)
|
||||
if not av:
|
||||
LOG.warning(f"Received AvatarAppearance with no avatar {sender_id}")
|
||||
return
|
||||
|
||||
version = message["AppearanceData"]["CofVersion"]
|
||||
if version < av.COFVersion:
|
||||
LOG.warning(f"Ignoring stale appearance for {sender_id}, {version} < {av.COFVersion}")
|
||||
return
|
||||
|
||||
if not message.get_blocks("VisualParam"):
|
||||
LOG.warning(f"No visual params in AvatarAppearance for {sender_id}")
|
||||
return
|
||||
|
||||
av.COFVersion = version
|
||||
av.Appearance = VISUAL_PARAMS.parse_appearance_message(message)
|
||||
|
||||
av_obj = av.Object
|
||||
if av_obj:
|
||||
self._run_object_update_hooks(av_obj, set(), ObjectUpdateType.APPEARANCE, message)
|
||||
|
||||
def _process_get_object_cost_response(self, parsed: dict):
|
||||
if "error" in parsed:
|
||||
return
|
||||
@@ -953,6 +983,8 @@ class Avatar:
|
||||
self.Object: Optional["Object"] = obj
|
||||
self.RegionHandle: int = region_handle
|
||||
self.CoarseLocation = coarse_location
|
||||
self.Appearance: Dict[int, float] = {}
|
||||
self.COFVersion: int = -1
|
||||
self.Valid = True
|
||||
self.GuessedZ: Optional[float] = None
|
||||
self._resolved_name = resolved_name
|
||||
|
||||
@@ -360,7 +360,7 @@ class MITMProxyEventManager:
|
||||
sim_seed = event["body"]["seed-capability"]
|
||||
# We teleported or cross region, opening comms to new sim
|
||||
elif msg and msg.name in ("TeleportFinish", "CrossedRegion"):
|
||||
sim_block = msg.get_block("RegionData", msg.get_block("Info"))[0]
|
||||
sim_block = msg.get_blocks("RegionData", msg.get_blocks("Info"))[0]
|
||||
sim_addr = (sim_block["SimIP"], sim_block["SimPort"])
|
||||
sim_handle = sim_block["RegionHandle"]
|
||||
sim_seed = sim_block["SeedCapability"]
|
||||
|
||||
@@ -4,6 +4,7 @@ import unittest
|
||||
|
||||
from hippolyzer.lib.base.datatypes import *
|
||||
from hippolyzer.lib.base.inventory import InventoryModel, SaleType, InventoryItem
|
||||
from hippolyzer.lib.base.message.message import Block, Message
|
||||
from hippolyzer.lib.base.wearables import Wearable, VISUAL_PARAMS
|
||||
|
||||
SIMPLE_INV = """\tinv_object\t0
|
||||
@@ -323,6 +324,270 @@ parameters 82
|
||||
textures 0
|
||||
"""
|
||||
|
||||
# TODO: Move appearance-related stuff elsewhere.
|
||||
|
||||
GIRL_NEXT_DOOR_APPEARANCE_MSG = Message(
|
||||
'AvatarAppearance',
|
||||
Block('Sender', ID=UUID(int=1), IsTrial=0),
|
||||
# We don't care about the value of this.
|
||||
Block('ObjectData', TextureEntry=b""),
|
||||
Block('VisualParam', ParamValue=9),
|
||||
Block('VisualParam', ParamValue=30),
|
||||
Block('VisualParam', ParamValue=71),
|
||||
Block('VisualParam', ParamValue=32),
|
||||
Block('VisualParam', ParamValue=51),
|
||||
Block('VisualParam', ParamValue=132),
|
||||
Block('VisualParam', ParamValue=10),
|
||||
Block('VisualParam', ParamValue=76),
|
||||
Block('VisualParam', ParamValue=84),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=43),
|
||||
Block('VisualParam', ParamValue=83),
|
||||
Block('VisualParam', ParamValue=113),
|
||||
Block('VisualParam', ParamValue=68),
|
||||
Block('VisualParam', ParamValue=73),
|
||||
Block('VisualParam', ParamValue=43),
|
||||
Block('VisualParam', ParamValue=35),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=7),
|
||||
Block('VisualParam', ParamValue=132),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=76),
|
||||
Block('VisualParam', ParamValue=91),
|
||||
Block('VisualParam', ParamValue=129),
|
||||
Block('VisualParam', ParamValue=106),
|
||||
Block('VisualParam', ParamValue=76),
|
||||
Block('VisualParam', ParamValue=58),
|
||||
Block('VisualParam', ParamValue=99),
|
||||
Block('VisualParam', ParamValue=73),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=203),
|
||||
Block('VisualParam', ParamValue=48),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=150),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=114),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=76),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=40),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=140),
|
||||
Block('VisualParam', ParamValue=86),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=99),
|
||||
Block('VisualParam', ParamValue=84),
|
||||
Block('VisualParam', ParamValue=53),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=66),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=100),
|
||||
Block('VisualParam', ParamValue=216),
|
||||
Block('VisualParam', ParamValue=214),
|
||||
Block('VisualParam', ParamValue=204),
|
||||
Block('VisualParam', ParamValue=204),
|
||||
Block('VisualParam', ParamValue=204),
|
||||
Block('VisualParam', ParamValue=51),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=89),
|
||||
Block('VisualParam', ParamValue=109),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=61),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=115),
|
||||
Block('VisualParam', ParamValue=76),
|
||||
Block('VisualParam', ParamValue=91),
|
||||
Block('VisualParam', ParamValue=158),
|
||||
Block('VisualParam', ParamValue=102),
|
||||
Block('VisualParam', ParamValue=109),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=193),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=132),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=68),
|
||||
Block('VisualParam', ParamValue=35),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=97),
|
||||
Block('VisualParam', ParamValue=92),
|
||||
Block('VisualParam', ParamValue=79),
|
||||
Block('VisualParam', ParamValue=107),
|
||||
Block('VisualParam', ParamValue=160),
|
||||
Block('VisualParam', ParamValue=112),
|
||||
Block('VisualParam', ParamValue=63),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=159),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=73),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=102),
|
||||
Block('VisualParam', ParamValue=158),
|
||||
Block('VisualParam', ParamValue=145),
|
||||
Block('VisualParam', ParamValue=153),
|
||||
Block('VisualParam', ParamValue=163),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=122),
|
||||
Block('VisualParam', ParamValue=43),
|
||||
Block('VisualParam', ParamValue=94),
|
||||
Block('VisualParam', ParamValue=135),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=214),
|
||||
Block('VisualParam', ParamValue=204),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=56),
|
||||
Block('VisualParam', ParamValue=30),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=204),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=112),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=25),
|
||||
Block('VisualParam', ParamValue=100),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=84),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=51),
|
||||
Block('VisualParam', ParamValue=94),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=255),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=25),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=25),
|
||||
Block('VisualParam', ParamValue=23),
|
||||
Block('VisualParam', ParamValue=51),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=25),
|
||||
Block('VisualParam', ParamValue=23),
|
||||
Block('VisualParam', ParamValue=51),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=25),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=25),
|
||||
Block('VisualParam', ParamValue=23),
|
||||
Block('VisualParam', ParamValue=51),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=25),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=25),
|
||||
Block('VisualParam', ParamValue=23),
|
||||
Block('VisualParam', ParamValue=51),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=25),
|
||||
Block('VisualParam', ParamValue=23),
|
||||
Block('VisualParam', ParamValue=51),
|
||||
Block('VisualParam', ParamValue=0),
|
||||
Block('VisualParam', ParamValue=25),
|
||||
Block('VisualParam', ParamValue=23),
|
||||
Block('VisualParam', ParamValue=51),
|
||||
Block('VisualParam', ParamValue=1),
|
||||
Block('VisualParam', ParamValue=127),
|
||||
Block('AppearanceData', AppearanceVersion=1, CofVersion=100, Flags=0),
|
||||
Block('AppearanceHover', HoverHeight=Vector3(0.0, 0.0, 0.0))
|
||||
)
|
||||
|
||||
|
||||
class TestWearable(unittest.TestCase):
|
||||
def test_parse(self):
|
||||
@@ -338,3 +603,17 @@ class TestWearable(unittest.TestCase):
|
||||
def test_visual_params(self):
|
||||
param = VISUAL_PARAMS.by_name("Eyelid_Inner_Corner_Up")
|
||||
self.assertEqual(param.value_max, 1.2)
|
||||
|
||||
def test_message_equivalent(self):
|
||||
wearable = Wearable.from_str(GIRL_NEXT_DOOR_SHAPE)
|
||||
parsed = VISUAL_PARAMS.parse_appearance_message(GIRL_NEXT_DOOR_APPEARANCE_MSG)
|
||||
|
||||
for i, (param_id, param_val) in enumerate(parsed.items()):
|
||||
param = VISUAL_PARAMS.by_id(param_id)
|
||||
if param.wearable != "shape":
|
||||
continue
|
||||
# A parameter may legitimately be missing from the shape depending on its age,
|
||||
# just assume it's the default value.
|
||||
expected_val = wearable.parameters.get(param_id, param.value_default)
|
||||
# This seems like quite a large delta. Maybe we should be using different quantization here.
|
||||
self.assertAlmostEqual(expected_val, param_val, delta=0.015)
|
||||
|
||||
Reference in New Issue
Block a user