Split out RLV handling
This commit is contained in:
@@ -152,7 +152,7 @@ class DeformerAddon(BaseAddon):
|
||||
local_anim.LocalAnimAddon.apply_local_anim(session, region, "deformer_addon", anim_data)
|
||||
|
||||
def handle_rlv_command(self, session: Session, region: ProxiedRegion, source: UUID,
|
||||
cmd: str, options: List[str], param: str):
|
||||
behaviour: str, options: List[str], param: str):
|
||||
# An object in-world can also tell the client how to deform itself via
|
||||
# RLV-style commands.
|
||||
|
||||
@@ -160,9 +160,9 @@ class DeformerAddon(BaseAddon):
|
||||
if param != "force":
|
||||
return
|
||||
|
||||
if cmd == "stop_deforming":
|
||||
if behaviour == "stop_deforming":
|
||||
self.deform_joints.clear()
|
||||
elif cmd == "deform_joints":
|
||||
elif behaviour == "deform_joints":
|
||||
self.deform_joints.clear()
|
||||
for joint_data in options:
|
||||
joint_split = joint_data.split("|")
|
||||
|
||||
@@ -114,15 +114,15 @@ class LocalAnimAddon(BaseAddon):
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
def handle_rlv_command(self, session: Session, region: ProxiedRegion, source: UUID,
|
||||
cmd: str, options: List[str], param: str):
|
||||
behaviour: str, options: List[str], param: str):
|
||||
# We only handle commands
|
||||
if param != "force":
|
||||
return
|
||||
|
||||
if cmd == "stop_local_anim":
|
||||
if behaviour == "stop_local_anim":
|
||||
self.apply_local_anim(session, region, options[0], new_data=None)
|
||||
return True
|
||||
elif cmd == "start_local_anim":
|
||||
elif behaviour == "start_local_anim":
|
||||
self.apply_local_anim_from_file(session, region, options[0])
|
||||
return True
|
||||
|
||||
|
||||
51
hippolyzer/lib/client/rlv.py
Normal file
51
hippolyzer/lib/client/rlv.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from typing import NamedTuple, List, Sequence
|
||||
|
||||
from hippolyzer.lib.base.message.message import Message
|
||||
from hippolyzer.lib.base.templates import ChatType
|
||||
|
||||
|
||||
class RLVCommand(NamedTuple):
|
||||
behaviour: str
|
||||
param: str
|
||||
options: List[str]
|
||||
|
||||
|
||||
class RLVParser:
|
||||
@staticmethod
|
||||
def is_rlv_message(msg: Message) -> bool:
|
||||
chat: str = msg["ChatData"]["Message"]
|
||||
chat_type: int = msg["ChatData"]["ChatType"]
|
||||
return chat and chat.startswith("@") and chat_type == ChatType.OWNER
|
||||
|
||||
@staticmethod
|
||||
def parse_chat(chat: str) -> List[RLVCommand]:
|
||||
assert chat.startswith("@")
|
||||
chat = chat.lstrip("@")
|
||||
commands = []
|
||||
for command_str in chat.split(","):
|
||||
if not command_str:
|
||||
continue
|
||||
# RLV-style command, `<cmd>(:<option1>;<option2>)?(=<param>)?`
|
||||
# Roughly (?<behaviour>[^:=]+)(:(?<option>[^=]*))?=(?<param>\w+)
|
||||
options, _, param = command_str.partition("=")
|
||||
behaviour, _, options = options.partition(":")
|
||||
# TODO: Not always correct, commands can specify their own parsing for the option field
|
||||
# maybe special-case these?
|
||||
options = options.split(";") if options else []
|
||||
commands.append(RLVCommand(behaviour, param, options))
|
||||
return commands
|
||||
|
||||
@staticmethod
|
||||
def format_chat(commands: Sequence[RLVCommand]) -> str:
|
||||
assert commands
|
||||
chat = ""
|
||||
for command in commands:
|
||||
if chat:
|
||||
chat += ","
|
||||
|
||||
chat += command.behaviour
|
||||
if command.options:
|
||||
chat += ":" + ";".join(command.options)
|
||||
if command.param:
|
||||
chat += "=" + command.param
|
||||
return "@" + chat
|
||||
@@ -188,7 +188,7 @@ class BaseAddon(metaclass=MetaBaseAddon):
|
||||
pass
|
||||
|
||||
def handle_rlv_command(self, session: Session, region: ProxiedRegion, source: UUID,
|
||||
cmd: str, options: List[str], param: str):
|
||||
behaviour: str, options: List[str], param: str):
|
||||
pass
|
||||
|
||||
def handle_proxied_packet(self, session_manager: SessionManager, packet: UDPPacket,
|
||||
|
||||
@@ -21,6 +21,7 @@ from hippolyzer.lib.base.datatypes import UUID
|
||||
from hippolyzer.lib.base.helpers import get_mtime
|
||||
from hippolyzer.lib.base.message.message import Message
|
||||
from hippolyzer.lib.base.network.transport import UDPPacket
|
||||
from hippolyzer.lib.client.rlv import RLVParser
|
||||
from hippolyzer.lib.proxy import addon_ctx
|
||||
from hippolyzer.lib.proxy.task_scheduler import TaskLifeScope, TaskScheduler
|
||||
|
||||
@@ -348,7 +349,7 @@ class AddonManager:
|
||||
cls.SCHEDULER.kill_matching_tasks(lifetime_mask=TaskLifeScope.ADDON, creator=addon)
|
||||
|
||||
@classmethod
|
||||
def _call_all_addon_hooks(cls, hook_name, *args, call_async=False, **kwargs):
|
||||
def _call_all_addon_hooks(cls, hook_name, *args, call_async=False, **kwargs) -> Optional[bool]:
|
||||
for module in cls.FRESH_ADDON_MODULES.values():
|
||||
if not module:
|
||||
continue
|
||||
@@ -391,7 +392,7 @@ class AddonManager:
|
||||
return cls._try_call_hook(module, hook_name, *args, call_async=call_async, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def _try_call_hook(cls, addon, hook_name, *args, call_async=False, **kwargs):
|
||||
def _try_call_hook(cls, addon, hook_name, *args, call_async=False, **kwargs) -> Optional[bool]:
|
||||
if cls._SUBPROCESS:
|
||||
return
|
||||
|
||||
@@ -452,32 +453,30 @@ class AddonManager:
|
||||
raise
|
||||
return True
|
||||
if message.name == "ChatFromSimulator" and "ChatData" in message:
|
||||
chat: str = message["ChatData"]["Message"]
|
||||
chat_type: int = message["ChatData"]["ChatType"]
|
||||
# RLV-style OwnerSay?
|
||||
if chat and chat.startswith("@") and chat_type == 8:
|
||||
if RLVParser.is_rlv_message(message):
|
||||
# RLV allows putting multiple commands into one message, blindly splitting on ",".
|
||||
chat = chat.lstrip("@")
|
||||
all_cmds_handled = True
|
||||
for command_str in chat.split(","):
|
||||
if not command_str:
|
||||
continue
|
||||
# RLV-style command, `@<cmd>(:<option1>;<option2>)?(=<param>)?`
|
||||
options, _, param = command_str.partition("=")
|
||||
cmd, _, options = options.partition(":")
|
||||
# TODO: Not always correct, commands can specify their own parsing for the option field
|
||||
options = options.split(";") if options else []
|
||||
source = message["ChatData"]["SourceID"]
|
||||
chat: str = message["ChatData"]["Message"]
|
||||
source = message["ChatData"]["SourceID"]
|
||||
for command in RLVParser.parse_chat(chat):
|
||||
try:
|
||||
with addon_ctx.push(session, region):
|
||||
handled = cls._call_all_addon_hooks("handle_rlv_command",
|
||||
session, region, source, cmd, options, param)
|
||||
handled = cls._call_all_addon_hooks(
|
||||
"handle_rlv_command",
|
||||
session,
|
||||
region,
|
||||
source,
|
||||
command.behaviour,
|
||||
command.options,
|
||||
command.param,
|
||||
)
|
||||
if handled:
|
||||
region.circuit.drop_message(message)
|
||||
else:
|
||||
all_cmds_handled = False
|
||||
except:
|
||||
LOG.exception(f"Failed while handling command {command_str!r}")
|
||||
LOG.exception(f"Failed while handling command {command!r}")
|
||||
all_cmds_handled = False
|
||||
if not cls._SWALLOW_ADDON_EXCEPTIONS:
|
||||
raise
|
||||
|
||||
36
tests/client/test_rlv.py
Normal file
36
tests/client/test_rlv.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import unittest
|
||||
|
||||
from hippolyzer.lib.base.message.message import Message, Block
|
||||
from hippolyzer.lib.base.templates import ChatType
|
||||
from hippolyzer.lib.client.rlv import RLVParser, RLVCommand
|
||||
|
||||
|
||||
class TestRLV(unittest.TestCase):
|
||||
def test_is_rlv_command(self):
|
||||
msg = Message(
|
||||
"ChatFromSimulator",
|
||||
Block("ChatData", Message="@foobar", ChatType=ChatType.OWNER)
|
||||
)
|
||||
self.assertTrue(RLVParser.is_rlv_message(msg))
|
||||
msg["ChatData"]["ChatType"] = ChatType.NORMAL
|
||||
self.assertFalse(RLVParser.is_rlv_message(msg))
|
||||
|
||||
def test_rlv_parse_single_command(self):
|
||||
cmd = RLVParser.parse_chat("@foo:bar;baz=quux")[0]
|
||||
self.assertEqual("foo", cmd.behaviour)
|
||||
self.assertListEqual(["bar", "baz"], cmd.options)
|
||||
self.assertEqual("quux", cmd.param)
|
||||
|
||||
def test_rlv_parse_multiple_commands(self):
|
||||
cmds = RLVParser.parse_chat("@foo:bar;baz=quux,bazzy")
|
||||
self.assertEqual("foo", cmds[0].behaviour)
|
||||
self.assertListEqual(["bar", "baz"], cmds[0].options)
|
||||
self.assertEqual("quux", cmds[0].param)
|
||||
self.assertEqual("bazzy", cmds[1].behaviour)
|
||||
|
||||
def test_rlv_format_commands(self):
|
||||
chat = RLVParser.format_chat([
|
||||
RLVCommand("foo", "quux", ["bar", "baz"]),
|
||||
RLVCommand("bazzy", "", [])
|
||||
])
|
||||
self.assertEqual("@foo:bar;baz=quux,bazzy", chat)
|
||||
Reference in New Issue
Block a user