Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a5b471fe4 | ||
|
|
ff0f20d1dd | ||
|
|
4898c852c1 | ||
|
|
adf5295e2b | ||
|
|
7514baaa5f | ||
|
|
0ba1a779ef | ||
|
|
3ea8a27914 |
2
.github/workflows/bundle_windows.yml
vendored
2
.github/workflows/bundle_windows.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.11]
|
||||
python-version: ["3.11"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
2
.github/workflows/pypi_publish.yml
vendored
2
.github/workflows/pypi_publish.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.10
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
@@ -341,6 +341,21 @@ class Message:
|
||||
msg.acks = dict_val['acks']
|
||||
return msg
|
||||
|
||||
@classmethod
|
||||
def from_eq_event(cls, event) -> Message:
|
||||
# If this isn't a templated message (like some EQ-only events are),
|
||||
# then we wrap it in a synthetic `Message` so that the API for handling
|
||||
# both EQ-only and templated message events can be the same. Ick.
|
||||
msg = cls(event["message"])
|
||||
if isinstance(event["body"], dict):
|
||||
msg.add_block(Block("EventData", **event["body"]))
|
||||
else:
|
||||
# Shouldn't be any events that have anything other than a dict
|
||||
# as a body, but just to be sure...
|
||||
msg.add_block(Block("EventData", Data=event["body"]))
|
||||
msg.synthetic = True
|
||||
return msg
|
||||
|
||||
def invalidate_caches(self):
|
||||
# Don't have any caches if we haven't even parsed
|
||||
if self.raw_body:
|
||||
|
||||
@@ -12,11 +12,14 @@ import math
|
||||
import zlib
|
||||
from typing import *
|
||||
|
||||
import numpy as np
|
||||
|
||||
import hippolyzer.lib.base.serialization as se
|
||||
from hippolyzer.lib.base import llsd
|
||||
from hippolyzer.lib.base.datatypes import UUID, IntEnum, IntFlag, Vector3, Quaternion
|
||||
from hippolyzer.lib.base.helpers import BiDiDict
|
||||
from hippolyzer.lib.base.namevalue import NameValuesSerializer
|
||||
from hippolyzer.lib.base.serialization import ParseContext
|
||||
|
||||
|
||||
class LookupIntEnum(IntEnum):
|
||||
@@ -2272,6 +2275,30 @@ class ParcelOverlaySerializer(se.SimpleSubfieldSerializer):
|
||||
TEMPLATE = se.Collection(None, se.BitfieldDataclass(ParcelGridInfo))
|
||||
|
||||
|
||||
class BitmapAdapter(se.Adapter):
|
||||
def __init__(self, shape: Tuple[int, int]):
|
||||
super().__init__(None)
|
||||
self._shape = shape
|
||||
|
||||
def encode(self, val: Any, ctx: Optional[ParseContext]) -> Any:
|
||||
if val and isinstance(val[0], bytes):
|
||||
return b''.join(val)
|
||||
return np.packbits(np.array(val, dtype=np.uint8).flatten(), bitorder="little").tobytes()
|
||||
|
||||
def decode(self, val: Any, ctx: Optional[ParseContext], pod: bool = False) -> Any:
|
||||
if pod:
|
||||
return [val[i:i + (self._shape[1] // 8)] for i in range(0, len(val), (self._shape[1] // 8))]
|
||||
parcel_bitmap = np.frombuffer(val, dtype=np.uint8)
|
||||
# This is a boolean array where each bit says whether the parcel occupies that grid.
|
||||
return np.unpackbits(parcel_bitmap, bitorder="little").reshape(self._shape)
|
||||
|
||||
|
||||
@se.subfield_serializer("ParcelProperties", "ParcelData", "Bitmap")
|
||||
class ParcelPropertiesBitmapSerializer(se.AdapterSubfieldSerializer):
|
||||
"""Bitmap that describes which grids a parcel occupies"""
|
||||
ADAPTER = BitmapAdapter((256 // 4, 256 // 4))
|
||||
|
||||
|
||||
@se.enum_field_serializer("ParcelProperties", "ParcelData", "LandingType")
|
||||
class LandingType(IntEnum):
|
||||
NONE = 1
|
||||
|
||||
@@ -285,17 +285,7 @@ class HippoClientRegion(BaseClientRegion):
|
||||
if self._llsd_serializer.can_handle(event["message"]):
|
||||
msg = self._llsd_serializer.deserialize(event)
|
||||
else:
|
||||
# If this isn't a templated message (like some EQ-only events are),
|
||||
# then we wrap it in a synthetic `Message` so that the API for handling
|
||||
# both EQ-only and templated message events can be the same. Ick.
|
||||
msg = Message(event["message"])
|
||||
if isinstance(event["body"], dict):
|
||||
msg.add_block(Block("EventData", **event["body"]))
|
||||
else:
|
||||
# Shouldn't be any events that have anything other than a dict
|
||||
# as a body, but just to be sure...
|
||||
msg.add_block(Block("EventData", Data=event["body"]))
|
||||
msg.synthetic = True
|
||||
msg = Message.from_eq_event(event)
|
||||
msg.sender = self.circuit_addr
|
||||
msg.direction = Direction.IN
|
||||
self.session().message_handler.handle(msg)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import logging
|
||||
from typing import *
|
||||
|
||||
import numpy as np
|
||||
@@ -10,6 +11,9 @@ from hippolyzer.lib.base.templates import ParcelGridFlags, ParcelFlags
|
||||
from hippolyzer.lib.client.state import BaseClientRegion
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Parcel:
|
||||
local_id: int
|
||||
@@ -57,7 +61,8 @@ class ParcelManager:
|
||||
self._parcels_dirty = False
|
||||
if new_overlay_data != self.overlay.data[:]:
|
||||
# If the raw data doesn't match, then we have to parse again
|
||||
self.overlay.data = new_overlay_data
|
||||
new_data = np.frombuffer(new_overlay_data, dtype=np.uint8).reshape(self.overlay.shape)
|
||||
np.copyto(self.overlay, new_data)
|
||||
self._parse_overlay()
|
||||
# We could optimize this by just marking specific squares dirty
|
||||
# if the parcel indices have changed between parses, but I don't care
|
||||
@@ -194,13 +199,30 @@ class ParcelManager:
|
||||
))
|
||||
self._next_seq += 1
|
||||
|
||||
parcel_props = await parcel_props_fut
|
||||
data_block = parcel_props["ParcelData"][0]
|
||||
# Parcel indices are one-indexed, convert to zero-indexed.
|
||||
parcel_idx = self.parcel_indices[self._pos_to_grid_coords(pos)] - 1
|
||||
assert len(self.parcels) > parcel_idx
|
||||
return self._process_parcel_properties(await parcel_props_fut, pos)
|
||||
|
||||
self.parcels[parcel_idx] = parcel = Parcel(
|
||||
def _process_parcel_properties(self, parcel_props: Message, pos: Optional[Vector2] = None) -> Parcel:
|
||||
data_block = parcel_props["ParcelData"][0]
|
||||
grid_coord = None
|
||||
# Parcel indices are one-indexed, convert to zero-indexed.
|
||||
if pos is not None:
|
||||
# We have a pos, figure out where in the grid we should look for the parcel index
|
||||
grid_coord = self._pos_to_grid_coords(pos)
|
||||
else:
|
||||
# Need to look at the parcel bitmap to figure out a valid grid coord.
|
||||
# This is a boolean array where each bit says whether the parcel occupies that grid.
|
||||
parcel_bitmap = data_block.deserialize_var("Bitmap")
|
||||
|
||||
for y in range(self.GRIDS_PER_EDGE):
|
||||
for x in range(self.GRIDS_PER_EDGE):
|
||||
if parcel_bitmap[y, x]:
|
||||
# This is the first grid the parcel occupies per the bitmap
|
||||
grid_coord = y, x
|
||||
break
|
||||
if grid_coord:
|
||||
break
|
||||
|
||||
parcel = Parcel(
|
||||
local_id=data_block["LocalID"],
|
||||
name=data_block["Name"],
|
||||
flags=ParcelFlags(data_block["ParcelFlags"]),
|
||||
@@ -208,4 +230,22 @@ class ParcelManager:
|
||||
# Parcel UUID isn't in this response :/
|
||||
)
|
||||
|
||||
# I guess the bitmap _could_ be empty, but probably not.
|
||||
if grid_coord is not None:
|
||||
parcel_idx = self.parcel_indices[grid_coord] - 1
|
||||
if len(self.parcels) > parcel_idx >= 0:
|
||||
# Okay, parcels list is sane, place the parcel in there.
|
||||
self.parcels[parcel_idx] = parcel
|
||||
else:
|
||||
LOG.warning(f"Received ParcelProperties with incomplete overlay for {grid_coord!r}")
|
||||
|
||||
return parcel
|
||||
|
||||
async def get_parcel_at(self, pos: Vector2, request_if_missing: bool = True) -> Optional[Parcel]:
|
||||
grid_coord = self._pos_to_grid_coords(pos)
|
||||
parcel = None
|
||||
if parcel_idx := self.parcel_indices[grid_coord]:
|
||||
parcel = self.parcels[parcel_idx - 1]
|
||||
if request_if_missing and parcel is None:
|
||||
return await self.request_parcel_properties(pos)
|
||||
return parcel
|
||||
|
||||
@@ -16,6 +16,8 @@ import mitmproxy.http
|
||||
from hippolyzer.lib.base import llsd
|
||||
from hippolyzer.lib.base.datatypes import UUID
|
||||
from hippolyzer.lib.base.message.llsd_msg_serializer import LLSDMessageSerializer
|
||||
from hippolyzer.lib.base.message.message import Message
|
||||
from hippolyzer.lib.base.network.transport import Direction
|
||||
from hippolyzer.lib.proxy.addons import AddonManager
|
||||
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
|
||||
from hippolyzer.lib.proxy.caps import CapData, CapType
|
||||
@@ -32,6 +34,9 @@ def apply_security_monkeypatches():
|
||||
apply_security_monkeypatches()
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MITMProxyEventManager:
|
||||
"""
|
||||
Handles HTTP request and response events from the mitmproxy process
|
||||
@@ -58,7 +63,7 @@ class MITMProxyEventManager:
|
||||
try:
|
||||
await self.pump_proxy_event()
|
||||
except:
|
||||
logging.exception("Exploded when handling parsed packets")
|
||||
LOG.exception("Exploded when handling parsed packets")
|
||||
|
||||
async def pump_proxy_event(self):
|
||||
try:
|
||||
@@ -140,7 +145,7 @@ class MITMProxyEventManager:
|
||||
# Both the wrapper request and the actual asset server request went through
|
||||
# the proxy. Don't bother trying the redirect strategy anymore.
|
||||
self._asset_server_proxied = True
|
||||
logging.warning("noproxy not used, switching to URI rewrite strategy")
|
||||
LOG.warning("noproxy not used, switching to URI rewrite strategy")
|
||||
elif cap_data and cap_data.cap_name == "EventQueueGet":
|
||||
# HACK: The sim's EQ acking mechanism doesn't seem to actually work.
|
||||
# if the client drops the connection due to timeout before we can
|
||||
@@ -151,7 +156,7 @@ class MITMProxyEventManager:
|
||||
eq_manager = cap_data.region().eq_manager
|
||||
cached_resp = eq_manager.get_cached_poll_response(req_ack_id)
|
||||
if cached_resp:
|
||||
logging.warning("Had to serve a cached EventQueueGet due to client desync")
|
||||
LOG.warning("Had to serve a cached EventQueueGet due to client desync")
|
||||
flow.response = mitmproxy.http.Response.make(
|
||||
200,
|
||||
llsd.format_xml(cached_resp),
|
||||
@@ -215,7 +220,7 @@ class MITMProxyEventManager:
|
||||
try:
|
||||
message_logger.log_http_response(flow)
|
||||
except:
|
||||
logging.exception("Failed while logging HTTP flow")
|
||||
LOG.exception("Failed while logging HTTP flow")
|
||||
|
||||
# Don't process responses for requests or responses injected by the proxy.
|
||||
# We already processed it, it came from us!
|
||||
@@ -274,13 +279,13 @@ class MITMProxyEventManager:
|
||||
|
||||
if cap_data.cap_name == "Seed":
|
||||
parsed = llsd.parse_xml(flow.response.content)
|
||||
logging.debug("Got seed cap for %r : %r" % (cap_data, parsed))
|
||||
LOG.debug("Got seed cap for %r : %r" % (cap_data, parsed))
|
||||
region.update_caps(parsed)
|
||||
|
||||
# On LL's grid these URIs aren't unique across sessions or regions,
|
||||
# so we get request attribution by replacing them with a unique
|
||||
# alias URI.
|
||||
logging.debug("Replacing GetMesh caps with wrapped versions")
|
||||
LOG.debug("Replacing GetMesh caps with wrapped versions")
|
||||
wrappable_caps = {"GetMesh2", "GetMesh", "GetTexture", "ViewerAsset"}
|
||||
for cap_name in wrappable_caps:
|
||||
if cap_name in parsed:
|
||||
@@ -315,7 +320,7 @@ class MITMProxyEventManager:
|
||||
if "uploader" in parsed:
|
||||
region.register_cap(cap_data.cap_name + "Uploader", parsed["uploader"], CapType.TEMPORARY)
|
||||
except:
|
||||
logging.exception("OOPS, blew up in HTTP proxy!")
|
||||
LOG.exception("OOPS, blew up in HTTP proxy!")
|
||||
|
||||
def _handle_login_flow(self, flow: HippoHTTPFlow):
|
||||
resp = xmlrpc.client.loads(flow.response.content)[0][0] # type: ignore
|
||||
@@ -324,20 +329,30 @@ class MITMProxyEventManager:
|
||||
flow.cap_data = CapData("LoginRequest", session=weakref.ref(sess))
|
||||
|
||||
def _handle_eq_event(self, session: Session, region: ProxiedRegion, event: Dict[str, Any]):
|
||||
logging.debug("Event received on %r: %r" % (self, event))
|
||||
LOG.debug("Event received on %r: %r" % (self, event))
|
||||
message_logger = self.session_manager.message_logger
|
||||
if message_logger:
|
||||
message_logger.log_eq_event(session, region, event)
|
||||
|
||||
if self.llsd_message_serializer.can_handle(event["message"]):
|
||||
msg = self.llsd_message_serializer.deserialize(event)
|
||||
else:
|
||||
msg = Message.from_eq_event(event)
|
||||
msg.sender = region.circuit_addr
|
||||
msg.direction = Direction.IN
|
||||
|
||||
try:
|
||||
region.message_handler.handle(msg)
|
||||
except:
|
||||
LOG.exception("Failed while handling EQ message")
|
||||
|
||||
handle_event = AddonManager.handle_eq_event(session, region, event)
|
||||
if handle_event is True:
|
||||
# Addon handled the event and didn't want it sent to the viewer
|
||||
return True
|
||||
|
||||
msg = None
|
||||
# Handle events that inform us about new regions
|
||||
sim_addr, sim_handle, sim_seed = None, None, None
|
||||
if self.llsd_message_serializer.can_handle(event["message"]):
|
||||
msg = self.llsd_message_serializer.deserialize(event)
|
||||
# Sim is asking us to talk to a neighbour
|
||||
if event["message"] == "EstablishAgentCommunication":
|
||||
ip_split = event["body"]["sim-ip-and-port"].split(":")
|
||||
|
||||
@@ -48,6 +48,7 @@ class ProxyObjectManager(ClientObjectManager):
|
||||
"RequestMultipleObjects",
|
||||
self._handle_request_multiple_objects,
|
||||
)
|
||||
region.http_message_handler.subscribe("RenderMaterials", self._handle_render_materials)
|
||||
|
||||
def load_cache(self):
|
||||
if not self.may_use_vo_cache or self.cache_loaded:
|
||||
@@ -100,6 +101,13 @@ class ProxyObjectManager(ClientObjectManager):
|
||||
# Remove any queued cache misses that the viewer just requested for itself
|
||||
self.queued_cache_misses -= {b["ID"] for b in msg["ObjectData"]}
|
||||
|
||||
def _handle_render_materials(self, flow: HippoHTTPFlow):
|
||||
if flow.response.status_code != 200:
|
||||
return
|
||||
if flow.request.method not in ("GET", "POST"):
|
||||
return
|
||||
self._process_materials_response(flow.response.content)
|
||||
|
||||
|
||||
class ProxyWorldObjectManager(ClientWorldObjectManager):
|
||||
_session: Session
|
||||
|
||||
18
hippolyzer/lib/proxy/parcel_manager.py
Normal file
18
hippolyzer/lib/proxy/parcel_manager.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from typing import *
|
||||
|
||||
from hippolyzer.lib.base.helpers import proxify
|
||||
from hippolyzer.lib.base.message.message import Message
|
||||
from hippolyzer.lib.client.parcel_manager import ParcelManager
|
||||
if TYPE_CHECKING:
|
||||
from hippolyzer.lib.proxy.region import ProxiedRegion
|
||||
|
||||
|
||||
class ProxyParcelManager(ParcelManager):
|
||||
def __init__(self, region: "ProxiedRegion"):
|
||||
super().__init__(proxify(region))
|
||||
# Handle ParcelProperties messages that we didn't specifically ask for
|
||||
self._region.message_handler.subscribe("ParcelProperties", self._handle_parcel_properties)
|
||||
|
||||
def _handle_parcel_properties(self, msg: Message):
|
||||
self._process_parcel_properties(msg)
|
||||
return None
|
||||
@@ -21,6 +21,7 @@ from hippolyzer.lib.proxy.object_manager import ProxyObjectManager
|
||||
from hippolyzer.lib.base.transfer_manager import TransferManager
|
||||
from hippolyzer.lib.base.xfer_manager import XferManager
|
||||
from hippolyzer.lib.proxy.asset_uploader import ProxyAssetUploader
|
||||
from hippolyzer.lib.proxy.parcel_manager import ProxyParcelManager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from hippolyzer.lib.proxy.sessions import Session
|
||||
@@ -67,6 +68,7 @@ class ProxiedRegion(BaseClientRegion):
|
||||
self.xfer_manager = XferManager(proxify(self), self.session().secure_session_id)
|
||||
self.transfer_manager = TransferManager(proxify(self), session.agent_id, session.id)
|
||||
self.asset_uploader = ProxyAssetUploader(proxify(self))
|
||||
self.parcel_manager = ProxyParcelManager(proxify(self))
|
||||
self._recalc_caps()
|
||||
|
||||
@property
|
||||
|
||||
2
setup.py
2
setup.py
@@ -25,7 +25,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
here = path.abspath(path.dirname(__file__))
|
||||
|
||||
version = '0.15.0'
|
||||
version = '0.15.1'
|
||||
|
||||
with open(path.join(here, 'README.md')) as readme_fh:
|
||||
readme = readme_fh.read()
|
||||
|
||||
@@ -6,7 +6,8 @@ from typing import Dict
|
||||
from hippolyzer.lib.base.datatypes import UUID
|
||||
from hippolyzer.lib.base.message.message import Block, Message
|
||||
import hippolyzer.lib.base.serialization as se
|
||||
from hippolyzer.lib.base.templates import ParcelGridInfo, ParcelGridType, ParcelGridFlags
|
||||
from hippolyzer.lib.base.templates import ParcelGridInfo, ParcelGridType, ParcelGridFlags, \
|
||||
ParcelPropertiesBitmapSerializer
|
||||
from hippolyzer.lib.base.test_utils import soon
|
||||
from hippolyzer.lib.client.parcel_manager import ParcelManager
|
||||
|
||||
@@ -190,6 +191,68 @@ OVERLAY_CHUNKS = (
|
||||
b'\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02',
|
||||
)
|
||||
|
||||
BITMAPS = (
|
||||
b'\x07\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
|
||||
b'\xf8\xff\xff\xff\xff\xff\xff\x7f\xf8\xff\xff\xff\xff\xff\xff\xff\xf8\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff',
|
||||
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
)
|
||||
|
||||
|
||||
class TestParcelOverlay(unittest.IsolatedAsyncioTestCase):
|
||||
async def asyncSetUp(self):
|
||||
@@ -235,11 +298,36 @@ class TestParcelOverlay(unittest.IsolatedAsyncioTestCase):
|
||||
# HACK: Wait for requests to be sent out
|
||||
await asyncio.sleep(0.01)
|
||||
|
||||
for i in range(1, 4):
|
||||
for i in range(3):
|
||||
self.handler.handle(Message(
|
||||
"ParcelProperties",
|
||||
Block("ParcelData", LocalID=i, SequenceID=i, Name=str(i), GroupID=UUID.ZERO, ParcelFlags=0),
|
||||
Block(
|
||||
"ParcelData",
|
||||
LocalID=i + 1,
|
||||
SequenceID=i + 1,
|
||||
Name=str(i + 1),
|
||||
GroupID=UUID.ZERO,
|
||||
ParcelFlags=0,
|
||||
Bitmap=BITMAPS[i],
|
||||
),
|
||||
))
|
||||
await soon(req_task)
|
||||
self.assertEqual(3, len(self.parcel_manager.parcels))
|
||||
self.assertEqual("1", self.parcel_manager.parcels[0].name)
|
||||
|
||||
async def test_parcel_bitmap_equivalence(self):
|
||||
for msg in self.test_msgs:
|
||||
self.handler.handle(msg)
|
||||
|
||||
serializer = ParcelPropertiesBitmapSerializer()
|
||||
bitmaps = [serializer.deserialize(None, x) for x in BITMAPS]
|
||||
|
||||
for y in range(ParcelManager.GRID_STEP):
|
||||
for x in range(ParcelManager.GRID_STEP):
|
||||
parcel_idx = self.parcel_manager.parcel_indices[y, x] - 1
|
||||
for i, bitmap in enumerate(bitmaps):
|
||||
bmp_set = bitmap[y, x]
|
||||
if bmp_set and parcel_idx != i:
|
||||
raise AssertionError(f"Parcel {parcel_idx} unexpected set in Bitmap {i} at {y, x}")
|
||||
elif not bmp_set and parcel_idx == i:
|
||||
raise AssertionError(f"Parcel {parcel_idx} not set in Bitmap {i} at {y, x}")
|
||||
|
||||
Reference in New Issue
Block a user