Split out RLV handling

This commit is contained in:
Salad Dais
2023-12-20 00:49:16 +00:00
parent 81eae4edbf
commit bd67d6f19f
6 changed files with 111 additions and 25 deletions

View File

@@ -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("|")

View File

@@ -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

View 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

View File

@@ -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,

View File

@@ -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
View 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)