Track animations for avatars and objects
This commit is contained in:
@@ -125,12 +125,14 @@ class Object(recordclass.RecordClass, use_weakref=True): # type: ignore
|
|||||||
SitName: Optional[str] = None
|
SitName: Optional[str] = None
|
||||||
TextureID: Optional[List[UUID]] = None
|
TextureID: Optional[List[UUID]] = None
|
||||||
RegionHandle: Optional[int] = None
|
RegionHandle: Optional[int] = None
|
||||||
|
Animations: Optional[List[UUID]] = None
|
||||||
|
|
||||||
def __init__(self, **_kwargs):
|
def __init__(self, **_kwargs):
|
||||||
""" set up the object attributes """
|
""" set up the object attributes """
|
||||||
self.ExtraParams = self.ExtraParams or {} # Variable 1
|
self.ExtraParams = self.ExtraParams or {} # Variable 1
|
||||||
self.ObjectCosts = self.ObjectCosts or {}
|
self.ObjectCosts = self.ObjectCosts or {}
|
||||||
self.ChildIDs = []
|
self.ChildIDs = []
|
||||||
|
self.Animations = self.Animations or []
|
||||||
# Same as parent, contains weakref proxies.
|
# Same as parent, contains weakref proxies.
|
||||||
self.Children: List[Object] = []
|
self.Children: List[Object] = []
|
||||||
|
|
||||||
@@ -253,7 +255,7 @@ def normalize_object_update(block: Block, handle: int):
|
|||||||
# OwnerID is only set in this packet if a sound is playing. Don't allow
|
# OwnerID is only set in this packet if a sound is playing. Don't allow
|
||||||
# ObjectUpdates to clobber _real_ OwnerIDs we had from ObjectProperties
|
# ObjectUpdates to clobber _real_ OwnerIDs we had from ObjectProperties
|
||||||
# with a null UUID.
|
# with a null UUID.
|
||||||
if object_data["OwnerID"] == UUID():
|
if object_data["OwnerID"] == UUID.ZERO:
|
||||||
del object_data["OwnerID"]
|
del object_data["OwnerID"]
|
||||||
del object_data["Flags"]
|
del object_data["Flags"]
|
||||||
del object_data["Gain"]
|
del object_data["Gain"]
|
||||||
@@ -309,7 +311,7 @@ def normalize_object_update_compressed_data(data: bytes):
|
|||||||
compressed["SoundFlags"] = 0
|
compressed["SoundFlags"] = 0
|
||||||
compressed["SoundGain"] = 0.0
|
compressed["SoundGain"] = 0.0
|
||||||
compressed["SoundRadius"] = 0.0
|
compressed["SoundRadius"] = 0.0
|
||||||
compressed["Sound"] = UUID()
|
compressed["Sound"] = UUID.ZERO
|
||||||
if compressed["TextureEntry"] is None:
|
if compressed["TextureEntry"] is None:
|
||||||
compressed["TextureEntry"] = tmpls.TextureEntryCollection()
|
compressed["TextureEntry"] = tmpls.TextureEntryCollection()
|
||||||
|
|
||||||
@@ -323,7 +325,7 @@ def normalize_object_update_compressed_data(data: bytes):
|
|||||||
# Don't clobber OwnerID in case the object has a proper one from
|
# Don't clobber OwnerID in case the object has a proper one from
|
||||||
# a previous ObjectProperties. OwnerID isn't expected to be populated
|
# a previous ObjectProperties. OwnerID isn't expected to be populated
|
||||||
# on ObjectUpdates unless an attached sound is playing.
|
# on ObjectUpdates unless an attached sound is playing.
|
||||||
if object_data["OwnerID"] == UUID():
|
if object_data["OwnerID"] == UUID.ZERO:
|
||||||
del object_data["OwnerID"]
|
del object_data["OwnerID"]
|
||||||
return object_data
|
return object_data
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class ObjectUpdateType(enum.IntEnum):
|
|||||||
FAMILY = enum.auto()
|
FAMILY = enum.auto()
|
||||||
COSTS = enum.auto()
|
COSTS = enum.auto()
|
||||||
KILL = enum.auto()
|
KILL = enum.auto()
|
||||||
|
ANIMATIONS = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
class ClientObjectManager:
|
class ClientObjectManager:
|
||||||
@@ -132,7 +133,7 @@ class ClientObjectManager:
|
|||||||
# Need to wait until we get our reply
|
# Need to wait until we get our reply
|
||||||
fut = self.state.register_future(local_id, ObjectUpdateType.PROPERTIES)
|
fut = self.state.register_future(local_id, ObjectUpdateType.PROPERTIES)
|
||||||
else:
|
else:
|
||||||
# This was selected so we should already have up to date info
|
# This was selected so we should already have up-to-date info
|
||||||
fut = asyncio.Future()
|
fut = asyncio.Future()
|
||||||
fut.set_result(self.lookup_localid(local_id))
|
fut.set_result(self.lookup_localid(local_id))
|
||||||
futures.append(fut)
|
futures.append(fut)
|
||||||
@@ -261,6 +262,10 @@ class ClientWorldObjectManager:
|
|||||||
self._handle_object_properties_generic)
|
self._handle_object_properties_generic)
|
||||||
message_handler.subscribe("ObjectPropertiesFamily",
|
message_handler.subscribe("ObjectPropertiesFamily",
|
||||||
self._handle_object_properties_generic)
|
self._handle_object_properties_generic)
|
||||||
|
message_handler.subscribe("AvatarAnimation",
|
||||||
|
self._handle_animation_message)
|
||||||
|
message_handler.subscribe("ObjectAnimation",
|
||||||
|
self._handle_animation_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)
|
||||||
@@ -274,7 +279,7 @@ class ClientWorldObjectManager:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def all_avatars(self) -> Iterable[Avatar]:
|
def all_avatars(self) -> Iterable[Avatar]:
|
||||||
return tuple(self._avatars.values())
|
return list(self._avatars.values())
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self._fullid_lookup)
|
return len(self._fullid_lookup)
|
||||||
@@ -293,7 +298,7 @@ class ClientWorldObjectManager:
|
|||||||
def untrack_region_objects(self, handle: int):
|
def untrack_region_objects(self, handle: int):
|
||||||
"""Handle signal that a region object manager was just cleared"""
|
"""Handle signal that a region object manager was just cleared"""
|
||||||
# Make sure they're gone from our lookup table
|
# Make sure they're gone from our lookup table
|
||||||
for obj in tuple(self._fullid_lookup.values()):
|
for obj in list(self._fullid_lookup.values()):
|
||||||
if obj.RegionHandle == handle:
|
if obj.RegionHandle == handle:
|
||||||
del self._fullid_lookup[obj.FullID]
|
del self._fullid_lookup[obj.FullID]
|
||||||
if handle in self._region_managers:
|
if handle in self._region_managers:
|
||||||
@@ -609,6 +614,33 @@ class ClientWorldObjectManager:
|
|||||||
region_state.coarse_locations.update(coarse_locations)
|
region_state.coarse_locations.update(coarse_locations)
|
||||||
self._rebuild_avatar_objects()
|
self._rebuild_avatar_objects()
|
||||||
|
|
||||||
|
def _handle_animation_message(self, message: Message):
|
||||||
|
sender_id = message["Sender"]["ID"]
|
||||||
|
if message.name == "AvatarAnimation":
|
||||||
|
avatar = self._avatars.get(sender_id)
|
||||||
|
if not avatar:
|
||||||
|
LOG.warning(f"Received AvatarAnimation for unknown avatar {sender_id}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not avatar.Object:
|
||||||
|
LOG.warning(f"Received AvatarAnimation for avatar with no object {sender_id}")
|
||||||
|
return
|
||||||
|
|
||||||
|
obj = avatar.Object
|
||||||
|
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}")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
LOG.error(f"Unknown animation message type: {message.name}")
|
||||||
|
return
|
||||||
|
|
||||||
|
obj.Animations.clear()
|
||||||
|
for block in message["AnimationList"]:
|
||||||
|
obj.Animations.append(block["AnimID"])
|
||||||
|
self._run_object_update_hooks(obj, {"Animations"}, ObjectUpdateType.ANIMATIONS, 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
|
||||||
@@ -887,8 +919,6 @@ class Avatar:
|
|||||||
self.FullID: UUID = full_id
|
self.FullID: UUID = full_id
|
||||||
self.Object: Optional["Object"] = obj
|
self.Object: Optional["Object"] = obj
|
||||||
self.RegionHandle: int = region_handle
|
self.RegionHandle: int = region_handle
|
||||||
# TODO: Allow hooking into getZOffsets FS bridge response
|
|
||||||
# to fill in the Z axis if it's infinite
|
|
||||||
self.CoarseLocation = coarse_location
|
self.CoarseLocation = coarse_location
|
||||||
self.Valid = True
|
self.Valid = True
|
||||||
self.GuessedZ: Optional[float] = None
|
self.GuessedZ: Optional[float] = None
|
||||||
|
|||||||
Reference in New Issue
Block a user