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
|
||||
TextureID: Optional[List[UUID]] = None
|
||||
RegionHandle: Optional[int] = None
|
||||
Animations: Optional[List[UUID]] = None
|
||||
|
||||
def __init__(self, **_kwargs):
|
||||
""" set up the object attributes """
|
||||
self.ExtraParams = self.ExtraParams or {} # Variable 1
|
||||
self.ObjectCosts = self.ObjectCosts or {}
|
||||
self.ChildIDs = []
|
||||
self.Animations = self.Animations or []
|
||||
# Same as parent, contains weakref proxies.
|
||||
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
|
||||
# ObjectUpdates to clobber _real_ OwnerIDs we had from ObjectProperties
|
||||
# with a null UUID.
|
||||
if object_data["OwnerID"] == UUID():
|
||||
if object_data["OwnerID"] == UUID.ZERO:
|
||||
del object_data["OwnerID"]
|
||||
del object_data["Flags"]
|
||||
del object_data["Gain"]
|
||||
@@ -309,7 +311,7 @@ def normalize_object_update_compressed_data(data: bytes):
|
||||
compressed["SoundFlags"] = 0
|
||||
compressed["SoundGain"] = 0.0
|
||||
compressed["SoundRadius"] = 0.0
|
||||
compressed["Sound"] = UUID()
|
||||
compressed["Sound"] = UUID.ZERO
|
||||
if compressed["TextureEntry"] is None:
|
||||
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
|
||||
# a previous ObjectProperties. OwnerID isn't expected to be populated
|
||||
# on ObjectUpdates unless an attached sound is playing.
|
||||
if object_data["OwnerID"] == UUID():
|
||||
if object_data["OwnerID"] == UUID.ZERO:
|
||||
del object_data["OwnerID"]
|
||||
return object_data
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ class ObjectUpdateType(enum.IntEnum):
|
||||
FAMILY = enum.auto()
|
||||
COSTS = enum.auto()
|
||||
KILL = enum.auto()
|
||||
ANIMATIONS = enum.auto()
|
||||
|
||||
|
||||
class ClientObjectManager:
|
||||
@@ -132,7 +133,7 @@ class ClientObjectManager:
|
||||
# Need to wait until we get our reply
|
||||
fut = self.state.register_future(local_id, ObjectUpdateType.PROPERTIES)
|
||||
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.set_result(self.lookup_localid(local_id))
|
||||
futures.append(fut)
|
||||
@@ -261,6 +262,10 @@ class ClientWorldObjectManager:
|
||||
self._handle_object_properties_generic)
|
||||
message_handler.subscribe("ObjectPropertiesFamily",
|
||||
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]:
|
||||
return self._fullid_lookup.get(full_id, None)
|
||||
@@ -274,7 +279,7 @@ class ClientWorldObjectManager:
|
||||
|
||||
@property
|
||||
def all_avatars(self) -> Iterable[Avatar]:
|
||||
return tuple(self._avatars.values())
|
||||
return list(self._avatars.values())
|
||||
|
||||
def __len__(self):
|
||||
return len(self._fullid_lookup)
|
||||
@@ -293,7 +298,7 @@ class ClientWorldObjectManager:
|
||||
def untrack_region_objects(self, handle: int):
|
||||
"""Handle signal that a region object manager was just cleared"""
|
||||
# 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:
|
||||
del self._fullid_lookup[obj.FullID]
|
||||
if handle in self._region_managers:
|
||||
@@ -609,6 +614,33 @@ class ClientWorldObjectManager:
|
||||
region_state.coarse_locations.update(coarse_locations)
|
||||
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):
|
||||
if "error" in parsed:
|
||||
return
|
||||
@@ -887,8 +919,6 @@ class Avatar:
|
||||
self.FullID: UUID = full_id
|
||||
self.Object: Optional["Object"] = obj
|
||||
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.Valid = True
|
||||
self.GuessedZ: Optional[float] = None
|
||||
|
||||
Reference in New Issue
Block a user