Files
Hippolyzer/addon_examples/local_anim.py
Salad Dais a39d025a04 Move Circuit and Message to lib.base
Fairly invasive, but will help make lib.base useful again. No
more Message / ProxiedMessage split!
2021-06-03 07:00:32 +00:00

165 lines
5.9 KiB
Python

"""
Local animations
/524 load_local_anim
assuming you loaded something.anim
/524 start_local_anim something
/524 stop_local_anim something
If you want to trigger the animation from an object to simulate llStartAnimation():
llOwnerSay("@start_local_anim:something=force");
"""
import asyncio
import os
import pathlib
from typing import *
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.addon_utils import BaseAddon, SessionProperty
from hippolyzer.lib.proxy.commands import handle_command
from hippolyzer.lib.proxy.http_asset_repo import HTTPAssetRepo
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
def _get_mtime(path: str):
try:
return os.stat(path).st_mtime
except:
return None
class LocalAnimAddon(BaseAddon):
# name -> path, only for anims actually from files
local_anim_paths: Dict[str, str] = SessionProperty(dict)
# name -> mtime or None. Only for anims from files.
local_anim_mtimes: Dict[str, Optional[float]] = SessionProperty(dict)
# name -> current asset ID (changes each play)
local_anim_playing_ids: Dict[str, UUID] = SessionProperty(dict)
def handle_session_init(self, session: Session):
self._schedule_task(self._try_reload_anims(session))
@handle_command()
async def load_local_anim(self, _session: Session, _region: ProxiedRegion):
"""Load a local animation file into the list of local anims"""
filename = await AddonManager.UI.open_file(filter_str="SL Anim (*.anim)")
if filename:
p = pathlib.Path(filename)
self.local_anim_paths[p.stem] = filename
@handle_command(anim_name=str)
async def start_local_anim(self, session: Session, region: ProxiedRegion, anim_name):
"""
Start a named local animation
Assuming you loaded an animation named something.anim:
start_local_anim something
"""
self.apply_local_anim_from_file(session, region, anim_name)
@handle_command(anim_name=str)
async def stop_local_anim(self, session: Session, region: ProxiedRegion, anim_name):
"""Stop a named local animation"""
self.apply_local_anim(session, region, anim_name, new_data=None)
async def _try_reload_anims(self, session: Session):
while True:
region = session.main_region
if not region:
await asyncio.sleep(2.0)
continue
# Loop over local anims we loaded
for anim_name, anim_id in self.local_anim_paths.items():
anim_id = self.local_anim_playing_ids.get(anim_name)
if not anim_id:
continue
# is playing right now, check if there's a newer version
self.apply_local_anim_from_file(session, region, anim_name, only_if_changed=True)
await asyncio.sleep(2.0)
def handle_rlv_command(self, session: Session, region: ProxiedRegion, source: UUID,
cmd: str, options: List[str], param: str):
# We only handle commands
if param != "force":
return
if cmd == "stop_local_anim":
self.apply_local_anim(session, region, options[0], new_data=None)
return True
elif cmd == "start_local_anim":
self.apply_local_anim_from_file(session, region, options[0])
return True
@classmethod
def apply_local_anim(cls, session: Session, region: ProxiedRegion,
anim_name: str, new_data: Optional[bytes] = None):
asset_repo: HTTPAssetRepo = session.session_manager.asset_repo
next_id: Optional[UUID] = None
new_msg = Message(
"AgentAnimation",
Block(
"AgentData",
AgentID=session.agent_id,
SessionID=session.id,
),
)
# Stop any old version of the anim that might be playing first
cur_id = cls.local_anim_playing_ids.get(anim_name)
if cur_id:
new_msg.add_block(Block(
"AnimationList",
AnimID=cur_id,
StartAnim=False,
))
if new_data is not None:
# Create a temp asset ID for the new version and send out a start request
next_id = asset_repo.create_asset(new_data, one_shot=True)
new_msg.add_block(Block(
"AnimationList",
AnimID=next_id,
StartAnim=True,
))
cls.local_anim_playing_ids[anim_name] = next_id
else:
# No data means just stop the anim
cls.local_anim_playing_ids.pop(anim_name, None)
region.circuit.send_message(new_msg)
print(f"Changing {anim_name} to {next_id}")
@classmethod
def apply_local_anim_from_file(cls, session: Session, region: ProxiedRegion,
anim_name: str, only_if_changed=False):
anim_path = cls.local_anim_paths.get(anim_name)
anim_data = None
if anim_path:
old_mtime = cls.local_anim_mtimes.get(anim_name)
mtime = _get_mtime(anim_path)
if only_if_changed and old_mtime == mtime:
return
cls.local_anim_mtimes[anim_name] = mtime
# file might not even exist anymore if mtime is `None`,
# anim will automatically stop if that happens.
if mtime:
if only_if_changed:
print(f"Re-applying {anim_name}")
else:
print(f"Playing {anim_name}")
with open(anim_path, "rb") as f:
anim_data = f.read()
else:
print(f"Unknown anim {anim_name!r}")
cls.apply_local_anim(session, region, anim_name, new_data=anim_data)
addons = [LocalAnimAddon()]