159 lines
6.9 KiB
Python
159 lines
6.9 KiB
Python
"""
|
|
Detect receipt of a marketplace order for a demo, and auto-attach the most appropriate object
|
|
"""
|
|
|
|
import asyncio
|
|
import re
|
|
from typing import List, Tuple, Dict, Optional, Sequence
|
|
|
|
from hippolyzer.lib.base.datatypes import UUID
|
|
from hippolyzer.lib.base.message.message import Message, Block
|
|
from hippolyzer.lib.base.templates import InventoryType, Permissions, FolderType
|
|
from hippolyzer.lib.proxy.addon_utils import BaseAddon, show_message
|
|
from hippolyzer.lib.proxy.region import ProxiedRegion
|
|
from hippolyzer.lib.proxy.sessions import Session
|
|
|
|
|
|
MARKETPLACE_TRANSACTION_ID = UUID('ffffffff-ffff-ffff-ffff-ffffffffffff')
|
|
|
|
|
|
class DemoAutoAttacher(BaseAddon):
|
|
def handle_eq_event(self, session: Session, region: ProxiedRegion, event: dict):
|
|
if event["message"] != "BulkUpdateInventory":
|
|
return
|
|
# Check that this update even possibly came from the marketplace
|
|
if event["body"]["AgentData"][0]["TransactionID"] != MARKETPLACE_TRANSACTION_ID:
|
|
return
|
|
# Make sure that the transaction targeted our real received items folder
|
|
folders = event["body"]["FolderData"]
|
|
received_folder = folders[0]
|
|
if received_folder["Name"] != "Received Items":
|
|
return
|
|
skel = session.login_data['inventory-skeleton']
|
|
actual_received = [x for x in skel if x['type_default'] == FolderType.INBOX]
|
|
assert actual_received
|
|
if UUID(actual_received[0]['folder_id']) != received_folder["FolderID"]:
|
|
show_message(f"Strange received folder ID spoofing? {folders!r}")
|
|
return
|
|
|
|
if not re.match(r".*\bdemo\b.*", folders[1]["Name"], flags=re.I):
|
|
return
|
|
# Alright, so we have a demo... thing from the marketplace. What now?
|
|
items = event["body"]["ItemData"]
|
|
object_items = [x for x in items if x["InvType"] == InventoryType.OBJECT]
|
|
if not object_items:
|
|
return
|
|
self._schedule_task(self._attach_best_object(session, region, object_items))
|
|
|
|
async def _attach_best_object(self, session: Session, region: ProxiedRegion, object_items: List[Dict]):
|
|
own_body_type = await self._guess_own_body(session, region)
|
|
show_message(f"Trying to find demo for {own_body_type}")
|
|
guess_patterns = self.BODY_CLOTHING_PATTERNS.get(own_body_type)
|
|
to_attach = []
|
|
if own_body_type and guess_patterns:
|
|
matching_items = self._get_matching_items(object_items, guess_patterns)
|
|
if matching_items:
|
|
# Only take the first one
|
|
to_attach.append(matching_items[0])
|
|
if not to_attach:
|
|
# Don't know what body's being used or couldn't figure out what item
|
|
# would work best with our body. Just attach the first object in the folder.
|
|
to_attach.append(object_items[0])
|
|
|
|
# Also attach whatever HUDs, maybe we need them.
|
|
for hud in self._get_matching_items(object_items, ("hud",)):
|
|
if hud not in to_attach:
|
|
to_attach.append(hud)
|
|
|
|
region.circuit.send(Message(
|
|
'RezMultipleAttachmentsFromInv',
|
|
Block('AgentData', AgentID=session.agent_id, SessionID=session.id),
|
|
Block('HeaderData', CompoundMsgID=UUID.random(), TotalObjects=len(to_attach), FirstDetachAll=0),
|
|
*[Block(
|
|
'ObjectData',
|
|
ItemID=o["ItemID"],
|
|
OwnerID=session.agent_id,
|
|
# 128 = "add", uses whatever attachmentpt was defined on the object
|
|
AttachmentPt=128,
|
|
ItemFlags_=(),
|
|
GroupMask_=(),
|
|
EveryoneMask_=(),
|
|
NextOwnerMask_=(Permissions.COPY | Permissions.MOVE),
|
|
Name=o["Name"],
|
|
Description=o["Description"],
|
|
) for o in to_attach]
|
|
))
|
|
|
|
def _get_matching_items(self, items: List[dict], patterns: Sequence[str]):
|
|
# Loop over patterns to search for our body type, in order of preference
|
|
matched = []
|
|
for guess_pattern in patterns:
|
|
# Check each item for that pattern
|
|
for item in items:
|
|
if re.match(rf".*\b{guess_pattern}\b.*", item["Name"], re.I):
|
|
matched.append(item)
|
|
return matched
|
|
|
|
# We scan the agent's attached objects to guess what kind of body they use
|
|
BODY_PREFIXES = {
|
|
"-Belleza- Jake ": "jake",
|
|
"-Belleza- Freya ": "freya",
|
|
"-Belleza- Isis ": "isis",
|
|
"-Belleza- Venus ": "venus",
|
|
"[Signature] Gianni Body": "gianni",
|
|
"[Signature] Geralt Body": "geralt",
|
|
"Maitreya Mesh Body - Lara": "maitreya",
|
|
"Slink Physique Hourglass Petite": "hg_petite",
|
|
"Slink Physique Mesh Body Hourglass": "hourglass",
|
|
"Slink Physique Original Petite": "phys_petite",
|
|
"Slink Physique Mesh Body Original": "physique",
|
|
"[BODY] Legacy (f)": "legacy_f",
|
|
"[BODY] Legacy (m)": "legacy_m",
|
|
"[Signature] Alice Body": "sig_alice",
|
|
"Slink Physique MALE Mesh Body": "slink_male",
|
|
"AESTHETIC - [Mesh Body]": "aesthetic",
|
|
}
|
|
|
|
# Different bodies' clothes have different naming conventions according to different merchants.
|
|
# These are common naming patterns we use to choose objects to attach, in order of preference.
|
|
BODY_CLOTHING_PATTERNS: Dict[str, Tuple[str, ...]] = {
|
|
"jake": ("jake", "belleza"),
|
|
"freya": ("freya", "belleza"),
|
|
"isis": ("isis", "belleza"),
|
|
"venus": ("venus", "belleza"),
|
|
"gianni": ("gianni", "signature", "sig"),
|
|
"geralt": ("geralt", "signature", "sig"),
|
|
"hg_petite": ("hourglass petite", "hg petite", "hourglass", "hg", "slink"),
|
|
"hourglass": ("hourglass", "hg", "slink"),
|
|
"phys_petite": ("physique petite", "phys petite", "physique", "phys", "slink"),
|
|
"physique": ("physique", "phys", "slink"),
|
|
"legacy_f": ("legacy",),
|
|
"legacy_m": ("legacy",),
|
|
"sig_alice": ("alice", "signature"),
|
|
"slink_male": ("physique", "slink"),
|
|
"aesthetic": ("aesthetic",),
|
|
}
|
|
|
|
async def _guess_own_body(self, session: Session, region: ProxiedRegion) -> Optional[str]:
|
|
agent_obj = region.objects.lookup_fullid(session.agent_id)
|
|
if not agent_obj:
|
|
return None
|
|
# We probably won't know the names for all of our attachments, request them.
|
|
# Could be obviated by looking at the COF, not worth it for this.
|
|
try:
|
|
await asyncio.wait(region.objects.request_object_properties(agent_obj.Children), timeout=0.5)
|
|
except asyncio.TimeoutError:
|
|
# We expect that we just won't ever receive some property requests, that's fine
|
|
pass
|
|
|
|
for prefix, body_type in self.BODY_PREFIXES.items():
|
|
for obj in agent_obj.Children:
|
|
if not obj.Name:
|
|
continue
|
|
if obj.Name.startswith(prefix):
|
|
return body_type
|
|
return None
|
|
|
|
|
|
addons = [DemoAutoAttacher()]
|