More inventory / wearables updates
This commit is contained in:
@@ -389,6 +389,21 @@ class InventoryPermissions(InventoryBase):
|
|||||||
# It's kind of redundant since it just means owner_id == NULL_KEY && group_id != NULL_KEY.
|
# It's kind of redundant since it just means owner_id == NULL_KEY && group_id != NULL_KEY.
|
||||||
is_owner_group: Optional[int] = schema_field(SchemaInt, default=None, llsd_only=True)
|
is_owner_group: Optional[int] = schema_field(SchemaInt, default=None, llsd_only=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def make_default(cls) -> Self:
|
||||||
|
return cls(
|
||||||
|
base_mask=0xFFffFFff,
|
||||||
|
owner_mask=0xFFffFFff,
|
||||||
|
group_mask=0,
|
||||||
|
everyone_mask=0,
|
||||||
|
next_owner_mask=0x82000,
|
||||||
|
creator_id=UUID.ZERO,
|
||||||
|
owner_id=UUID.ZERO,
|
||||||
|
last_owner_id=UUID.ZERO,
|
||||||
|
group_id=UUID.ZERO,
|
||||||
|
is_owner_group=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class InventorySaleInfo(InventoryBase):
|
class InventorySaleInfo(InventoryBase):
|
||||||
@@ -397,6 +412,10 @@ class InventorySaleInfo(InventoryBase):
|
|||||||
sale_type: SaleType = schema_field(SchemaEnumField(SaleType))
|
sale_type: SaleType = schema_field(SchemaEnumField(SaleType))
|
||||||
sale_price: int = schema_field(SchemaInt)
|
sale_price: int = schema_field(SchemaInt)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def make_default(cls) -> Self:
|
||||||
|
return cls(sale_type=SaleType.NOT, sale_price=10)
|
||||||
|
|
||||||
|
|
||||||
class _HasBaseNodeAttrs(abc.ABC):
|
class _HasBaseNodeAttrs(abc.ABC):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ class HippoLLSDXMLFormatter(base_llsd.serde_xml.LLSDXMLFormatter, HippoLLSDBaseF
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def _generate(self, something):
|
||||||
|
if isinstance(something, int) and type(something) is not int:
|
||||||
|
# The lookup in the underlying library will fail if we don't convert IntEnums to actual ints.
|
||||||
|
something = int(something)
|
||||||
|
return super()._generate(something)
|
||||||
|
|
||||||
|
|
||||||
class HippoLLSDXMLPrettyFormatter(base_llsd.serde_xml.LLSDXMLPrettyFormatter, HippoLLSDBaseFormatter):
|
class HippoLLSDXMLPrettyFormatter(base_llsd.serde_xml.LLSDXMLPrettyFormatter, HippoLLSDBaseFormatter):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ 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
|
||||||
from hippolyzer.lib.base.templates import WearableType
|
from hippolyzer.lib.base.templates import WearableType, AssetType
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
@@ -77,6 +77,12 @@ class AvatarTEIndex(enum.IntEnum):
|
|||||||
def is_baked(self) -> bool:
|
def is_baked(self) -> bool:
|
||||||
return self.name.endswith("_BAKED")
|
return self.name.endswith("_BAKED")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def asset_type(self) -> AssetType:
|
||||||
|
if self in (WearableType.HAIR, WearableType.SKIN, WearableType.EYES, WearableType.SHAPE):
|
||||||
|
return AssetType.BODYPART
|
||||||
|
return AssetType.CLOTHING
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class VisualParam:
|
class VisualParam:
|
||||||
@@ -84,6 +90,7 @@ class VisualParam:
|
|||||||
name: str
|
name: str
|
||||||
value_min: float
|
value_min: float
|
||||||
value_max: float
|
value_max: float
|
||||||
|
value_default: float
|
||||||
# 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]
|
||||||
@@ -102,6 +109,7 @@ class VisualParams(List[VisualParam]):
|
|||||||
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))
|
||||||
))
|
))
|
||||||
|
|
||||||
def by_name(self, name: str) -> VisualParam:
|
def by_name(self, name: str) -> VisualParam:
|
||||||
@@ -120,6 +128,34 @@ class VisualParams(List[VisualParam]):
|
|||||||
VISUAL_PARAMS = VisualParams(get_resource_filename("lib/base/data/avatar_lad.xml"))
|
VISUAL_PARAMS = VisualParams(get_resource_filename("lib/base/data/avatar_lad.xml"))
|
||||||
|
|
||||||
|
|
||||||
|
# See `llpaneleditwearable.cpp`, which TE slots should be set for each wearable type is hardcoded
|
||||||
|
# in the viewer.
|
||||||
|
WEARABLE_TEXTURE_SLOTS: Dict[WearableType, Sequence[AvatarTEIndex]] = {
|
||||||
|
WearableType.SHAPE: (),
|
||||||
|
WearableType.SKIN: (AvatarTEIndex.HEAD_BODYPAINT, AvatarTEIndex.UPPER_BODYPAINT, AvatarTEIndex.LOWER_BODYPAINT),
|
||||||
|
WearableType.HAIR: (AvatarTEIndex.HAIR,),
|
||||||
|
WearableType.EYES: (AvatarTEIndex.EYES_IRIS,),
|
||||||
|
WearableType.SHIRT: (AvatarTEIndex.UPPER_SHIRT,),
|
||||||
|
WearableType.PANTS: (AvatarTEIndex.LOWER_PANTS,),
|
||||||
|
WearableType.SHOES: (AvatarTEIndex.LOWER_SHOES,),
|
||||||
|
WearableType.SOCKS: (AvatarTEIndex.LOWER_SOCKS,),
|
||||||
|
WearableType.JACKET: (AvatarTEIndex.UPPER_JACKET, AvatarTEIndex.LOWER_JACKET),
|
||||||
|
WearableType.GLOVES: (AvatarTEIndex.UPPER_GLOVES,),
|
||||||
|
WearableType.UNDERSHIRT: (AvatarTEIndex.UPPER_UNDERSHIRT,),
|
||||||
|
WearableType.UNDERPANTS: (AvatarTEIndex.LOWER_UNDERPANTS,),
|
||||||
|
WearableType.SKIRT: (AvatarTEIndex.SKIRT,),
|
||||||
|
WearableType.ALPHA: (AvatarTEIndex.LOWER_ALPHA, AvatarTEIndex.UPPER_ALPHA,
|
||||||
|
AvatarTEIndex.HEAD_ALPHA, AvatarTEIndex.EYES_ALPHA, AvatarTEIndex.HAIR_ALPHA),
|
||||||
|
WearableType.TATTOO: (AvatarTEIndex.LOWER_TATTOO, AvatarTEIndex.UPPER_TATTOO, AvatarTEIndex.HEAD_TATTOO),
|
||||||
|
WearableType.UNIVERSAL: (AvatarTEIndex.HEAD_UNIVERSAL_TATTOO, AvatarTEIndex.UPPER_UNIVERSAL_TATTOO,
|
||||||
|
AvatarTEIndex.LOWER_UNIVERSAL_TATTOO, AvatarTEIndex.SKIRT_TATTOO,
|
||||||
|
AvatarTEIndex.HAIR_TATTOO, AvatarTEIndex.EYES_TATTOO, AvatarTEIndex.LEFT_ARM_TATTOO,
|
||||||
|
AvatarTEIndex.LEFT_LEG_TATTOO, AvatarTEIndex.AUX1_TATTOO, AvatarTEIndex.AUX2_TATTOO,
|
||||||
|
AvatarTEIndex.AUX3_TATTOO),
|
||||||
|
WearableType.PHYSICS: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Wearable(SchemaBase):
|
class Wearable(SchemaBase):
|
||||||
name: str
|
name: str
|
||||||
@@ -128,7 +164,7 @@ class Wearable(SchemaBase):
|
|||||||
sale_info: InventorySaleInfo
|
sale_info: InventorySaleInfo
|
||||||
# VisualParam ID -> val
|
# VisualParam ID -> val
|
||||||
parameters: Dict[int, float]
|
parameters: Dict[int, float]
|
||||||
# TextureEntry ID -> texture ID
|
# TextureEntry ID -> texture UUID
|
||||||
textures: Dict[int, UUID]
|
textures: Dict[int, UUID]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -203,3 +239,22 @@ class Wearable(SchemaBase):
|
|||||||
writer.write(f"textures {len(self.textures)}\n")
|
writer.write(f"textures {len(self.textures)}\n")
|
||||||
for te_id, texture_id in self.textures.items():
|
for te_id, texture_id in self.textures.items():
|
||||||
writer.write(f"{te_id} {texture_id}\n")
|
writer.write(f"{te_id} {texture_id}\n")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def make_default(cls, w_type: WearableType) -> Self:
|
||||||
|
instance = cls(
|
||||||
|
name="New " + w_type.name.replace("_", " ").title(),
|
||||||
|
permissions=InventoryPermissions.make_default(),
|
||||||
|
sale_info=InventorySaleInfo.make_default(),
|
||||||
|
parameters={},
|
||||||
|
textures={},
|
||||||
|
wearable_type=w_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
for te_idx in WEARABLE_TEXTURE_SLOTS[w_type]:
|
||||||
|
instance.textures[te_idx] = DEFAULT_WEARABLE_TEX
|
||||||
|
|
||||||
|
for param in VISUAL_PARAMS.by_wearable(w_type.name.lower()):
|
||||||
|
instance.parameters[param.id] = param.value_default
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|||||||
@@ -219,7 +219,8 @@ class InventoryManager:
|
|||||||
self.model.upsert(InventoryItem.from_llsd(link_llsd, flavor="ais"))
|
self.model.upsert(InventoryItem.from_llsd(link_llsd, flavor="ais"))
|
||||||
|
|
||||||
for cat_id, version in payload.get("_updated_category_versions", {}).items():
|
for cat_id, version in payload.get("_updated_category_versions", {}).items():
|
||||||
cat_node = self.model.get_category(cat_id)
|
# The key will be a string, so convert to UUID first
|
||||||
|
cat_node = self.model.get_category(UUID(cat_id))
|
||||||
cat_node.version = version
|
cat_node.version = version
|
||||||
|
|
||||||
# Get rid of anything we were asked to
|
# Get rid of anything we were asked to
|
||||||
|
|||||||
@@ -152,6 +152,15 @@ class TestDatatypes(unittest.TestCase):
|
|||||||
def test_str_llsd_serialization(self):
|
def test_str_llsd_serialization(self):
|
||||||
self.assertEqual(b"'foo\\nbar'", llsd.format_notation("foo\nbar"))
|
self.assertEqual(b"'foo\\nbar'", llsd.format_notation("foo\nbar"))
|
||||||
|
|
||||||
|
def test_int_enum_llsd_serialization(self):
|
||||||
|
class SomeIntEnum(IntEnum):
|
||||||
|
FOO = 4
|
||||||
|
|
||||||
|
orig = SomeIntEnum.FOO
|
||||||
|
val = llsd.parse_xml(llsd.format_xml(orig))
|
||||||
|
self.assertIsInstance(val, int)
|
||||||
|
self.assertEqual(orig, val)
|
||||||
|
|
||||||
def test_jank_stringy_bytes(self):
|
def test_jank_stringy_bytes(self):
|
||||||
val = JankStringyBytes(b"foo\x00")
|
val = JankStringyBytes(b"foo\x00")
|
||||||
self.assertTrue("o" in val)
|
self.assertTrue("o" in val)
|
||||||
|
|||||||
Reference in New Issue
Block a user