Start handling AvatarAppearance messages
This commit is contained in:
@@ -267,7 +267,7 @@ class Message:
|
|||||||
block.message_name = self.name
|
block.message_name = self.name
|
||||||
block.finalize()
|
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)
|
return self.blocks.get(block_name, default)
|
||||||
|
|
||||||
@property
|
@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.helpers import get_resource_filename
|
||||||
from hippolyzer.lib.base.inventory import InventorySaleInfo, InventoryPermissions
|
from hippolyzer.lib.base.inventory import InventorySaleInfo, InventoryPermissions
|
||||||
from hippolyzer.lib.base.legacy_schema import SchemaBase, parse_schema_line, SchemaParsingError
|
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
|
from hippolyzer.lib.base.templates import WearableType
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@@ -78,6 +80,13 @@ class AvatarTEIndex(enum.IntEnum):
|
|||||||
return self.name.endswith("_BAKED")
|
return self.name.endswith("_BAKED")
|
||||||
|
|
||||||
|
|
||||||
|
class VisualParamGroup(enum.IntEnum):
|
||||||
|
TWEAKABLE = 0
|
||||||
|
ANIMATABLE = 1
|
||||||
|
TWEAKABLE_NO_TRANSMIT = 2
|
||||||
|
TRANSMIT_NOT_TWEAKABLE = 3
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class VisualParam:
|
class VisualParam:
|
||||||
id: int
|
id: int
|
||||||
@@ -85,26 +94,47 @@ class VisualParam:
|
|||||||
value_min: float
|
value_min: float
|
||||||
value_max: float
|
value_max: float
|
||||||
value_default: float
|
value_default: float
|
||||||
|
group: VisualParamGroup
|
||||||
# These might be `None` if the param isn't meant to be directly edited
|
# These might be `None` if the param isn't meant to be directly edited
|
||||||
edit_group: Optional[str]
|
edit_group: Optional[str]
|
||||||
wearable: 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]):
|
class VisualParams(List[VisualParam]):
|
||||||
def __init__(self, lad_path):
|
def __init__(self, lad_path):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
with open(lad_path, "rb") as f:
|
with open(lad_path, "rb") as f:
|
||||||
doc = parse_etree(f)
|
doc = parse_etree(f)
|
||||||
|
|
||||||
|
temp_params = []
|
||||||
for param in doc.findall(".//param"):
|
for param in doc.findall(".//param"):
|
||||||
self.append(VisualParam(
|
temp_params.append(VisualParam(
|
||||||
id=int(param.attrib["id"]),
|
id=int(param.attrib["id"]),
|
||||||
name=param.attrib["name"],
|
name=param.attrib["name"],
|
||||||
|
group=VisualParamGroup(int(param.get("group", "0"))),
|
||||||
edit_group=param.get("edit_group"),
|
edit_group=param.get("edit_group"),
|
||||||
wearable=param.get("wearable"),
|
wearable=param.get("wearable"),
|
||||||
value_min=float(param.attrib["value_min"]),
|
value_min=float(param.attrib["value_min"]),
|
||||||
value_max=float(param.attrib["value_max"]),
|
value_max=float(param.attrib["value_max"]),
|
||||||
value_default=float(param.attrib.get("value_default", 0.0))
|
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:
|
def by_name(self, name: str) -> VisualParam:
|
||||||
return [x for x in self if x.name == name][0]
|
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:
|
def by_id(self, vparam_id: int) -> VisualParam:
|
||||||
return [x for x in self if x.id == vparam_id][0]
|
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"))
|
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"]
|
sim_seed = msg["EventData"]["seed-capability"]
|
||||||
# We teleported or cross region, opening comms to new sim
|
# We teleported or cross region, opening comms to new sim
|
||||||
elif msg.name in ("TeleportFinish", "CrossedRegion"):
|
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_addr = (sim_block["SimIP"], sim_block["SimPort"])
|
||||||
sim_handle = sim_block["RegionHandle"]
|
sim_handle = sim_block["RegionHandle"]
|
||||||
sim_seed = sim_block["SeedCapability"]
|
sim_seed = sim_block["SeedCapability"]
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from hippolyzer.lib.base.objects import (
|
|||||||
Object, handle_to_global_pos,
|
Object, handle_to_global_pos,
|
||||||
)
|
)
|
||||||
from hippolyzer.lib.base.settings import Settings
|
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.client.namecache import NameCache, NameCacheEntry
|
||||||
from hippolyzer.lib.base.templates import PCode, ObjectStateSerializer, XferFilePath
|
from hippolyzer.lib.base.templates import PCode, ObjectStateSerializer, XferFilePath
|
||||||
from hippolyzer.lib.base import llsd
|
from hippolyzer.lib.base import llsd
|
||||||
@@ -47,6 +48,7 @@ class ObjectUpdateType(enum.IntEnum):
|
|||||||
COSTS = enum.auto()
|
COSTS = enum.auto()
|
||||||
KILL = enum.auto()
|
KILL = enum.auto()
|
||||||
ANIMATIONS = enum.auto()
|
ANIMATIONS = enum.auto()
|
||||||
|
APPEARANCE = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
class ClientObjectManager:
|
class ClientObjectManager:
|
||||||
@@ -299,6 +301,8 @@ class ClientWorldObjectManager:
|
|||||||
self._handle_animation_message)
|
self._handle_animation_message)
|
||||||
message_handler.subscribe("ObjectAnimation",
|
message_handler.subscribe("ObjectAnimation",
|
||||||
self._handle_animation_message)
|
self._handle_animation_message)
|
||||||
|
message_handler.subscribe("AvatarAppearance",
|
||||||
|
self._handle_avatar_appearance_message)
|
||||||
|
|
||||||
def lookup_fullid(self, full_id: UUID) -> Optional[Object]:
|
def lookup_fullid(self, full_id: UUID) -> Optional[Object]:
|
||||||
return self._fullid_lookup.get(full_id, None)
|
return self._fullid_lookup.get(full_id, None)
|
||||||
@@ -663,7 +667,8 @@ class ClientWorldObjectManager:
|
|||||||
elif message.name == "ObjectAnimation":
|
elif message.name == "ObjectAnimation":
|
||||||
obj = self.lookup_fullid(sender_id)
|
obj = self.lookup_fullid(sender_id)
|
||||||
if not obj:
|
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
|
return
|
||||||
else:
|
else:
|
||||||
LOG.error(f"Unknown animation message type: {message.name}")
|
LOG.error(f"Unknown animation message type: {message.name}")
|
||||||
@@ -674,6 +679,31 @@ class ClientWorldObjectManager:
|
|||||||
obj.Animations.append(block["AnimID"])
|
obj.Animations.append(block["AnimID"])
|
||||||
self._run_object_update_hooks(obj, {"Animations"}, ObjectUpdateType.ANIMATIONS, message)
|
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):
|
def _process_get_object_cost_response(self, parsed: dict):
|
||||||
if "error" in parsed:
|
if "error" in parsed:
|
||||||
return
|
return
|
||||||
@@ -953,6 +983,8 @@ class Avatar:
|
|||||||
self.Object: Optional["Object"] = obj
|
self.Object: Optional["Object"] = obj
|
||||||
self.RegionHandle: int = region_handle
|
self.RegionHandle: int = region_handle
|
||||||
self.CoarseLocation = coarse_location
|
self.CoarseLocation = coarse_location
|
||||||
|
self.Appearance: Dict[int, float] = {}
|
||||||
|
self.COFVersion: int = -1
|
||||||
self.Valid = True
|
self.Valid = True
|
||||||
self.GuessedZ: Optional[float] = None
|
self.GuessedZ: Optional[float] = None
|
||||||
self._resolved_name = resolved_name
|
self._resolved_name = resolved_name
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ class MITMProxyEventManager:
|
|||||||
sim_seed = event["body"]["seed-capability"]
|
sim_seed = event["body"]["seed-capability"]
|
||||||
# We teleported or cross region, opening comms to new sim
|
# We teleported or cross region, opening comms to new sim
|
||||||
elif msg and msg.name in ("TeleportFinish", "CrossedRegion"):
|
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_addr = (sim_block["SimIP"], sim_block["SimPort"])
|
||||||
sim_handle = sim_block["RegionHandle"]
|
sim_handle = sim_block["RegionHandle"]
|
||||||
sim_seed = sim_block["SeedCapability"]
|
sim_seed = sim_block["SeedCapability"]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import unittest
|
|||||||
|
|
||||||
from hippolyzer.lib.base.datatypes import *
|
from hippolyzer.lib.base.datatypes import *
|
||||||
from hippolyzer.lib.base.inventory import InventoryModel, SaleType, InventoryItem
|
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
|
from hippolyzer.lib.base.wearables import Wearable, VISUAL_PARAMS
|
||||||
|
|
||||||
SIMPLE_INV = """\tinv_object\t0
|
SIMPLE_INV = """\tinv_object\t0
|
||||||
@@ -323,6 +324,270 @@ parameters 82
|
|||||||
textures 0
|
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):
|
class TestWearable(unittest.TestCase):
|
||||||
def test_parse(self):
|
def test_parse(self):
|
||||||
@@ -338,3 +603,17 @@ class TestWearable(unittest.TestCase):
|
|||||||
def test_visual_params(self):
|
def test_visual_params(self):
|
||||||
param = VISUAL_PARAMS.by_name("Eyelid_Inner_Corner_Up")
|
param = VISUAL_PARAMS.by_name("Eyelid_Inner_Corner_Up")
|
||||||
self.assertEqual(param.value_max, 1.2)
|
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