Read nearby 26.3 sign text
This commit is contained in:
@@ -324,6 +324,29 @@ class Commands:
|
|||||||
tree = next(self.g.world.find_trees(pos, 50))
|
tree = next(self.g.world.find_trees(pos, 50))
|
||||||
reply = str(tree)[1:-1]
|
reply = str(tree)[1:-1]
|
||||||
|
|
||||||
|
## !signs [distance] - prints nearby sign text
|
||||||
|
if command == 'signs':
|
||||||
|
distance = float(data) if data else 32
|
||||||
|
signs = self.g.world.find_signs(
|
||||||
|
utils.pint(self.g.pos), distance)
|
||||||
|
if signs:
|
||||||
|
results = []
|
||||||
|
for sign in signs:
|
||||||
|
text = ' | '.join(line for line in sign['lines']
|
||||||
|
if line.strip())
|
||||||
|
entry = '{} {}: {}'.format(
|
||||||
|
sign['side'], sign['position'], text)
|
||||||
|
results.append(entry)
|
||||||
|
print('Sign {} {}:'.format(
|
||||||
|
sign['side'], sign['position']))
|
||||||
|
for line in sign['lines']:
|
||||||
|
print(' ' + line)
|
||||||
|
reply = '; '.join(results)
|
||||||
|
if len(reply) > 240:
|
||||||
|
reply = reply[:237] + '...'
|
||||||
|
else:
|
||||||
|
reply = 'no signs found within {} blocks'.format(distance)
|
||||||
|
|
||||||
## !info [query] - replies with info on a coordinate, block, item, or player
|
## !info [query] - replies with info on a coordinate, block, item, or player
|
||||||
if command == 'info':
|
if command == 'info':
|
||||||
if not reply:
|
if not reply:
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ from munch import Munch
|
|||||||
|
|
||||||
from minecraft.networking.packets import clientbound, serverbound
|
from minecraft.networking.packets import clientbound, serverbound
|
||||||
from minecraft.networking.modern import (
|
from minecraft.networking.modern import (
|
||||||
AddEntityPacket, ContainerOpenPacket, ContainerSetContentPacket,
|
AddEntityPacket, BlockEntityDataPacket, ContainerOpenPacket,
|
||||||
ContainerSetSlotPacket, SetEntityDataPacket,
|
ContainerSetContentPacket, ContainerSetSlotPacket, SetEntityDataPacket,
|
||||||
|
nbt_plain,
|
||||||
PaletteSection,
|
PaletteSection,
|
||||||
EntityPositionSyncPacket, MoveEntityPositionPacket,
|
EntityPositionSyncPacket, MoveEntityPositionPacket,
|
||||||
MoveEntityPositionRotationPacket, MoveEntityRotationPacket,
|
MoveEntityPositionRotationPacket, MoveEntityRotationPacket,
|
||||||
@@ -218,6 +219,7 @@ class WorldState:
|
|||||||
self.min_section = min_y // 16
|
self.min_section = min_y // 16
|
||||||
self.chunks = {}
|
self.chunks = {}
|
||||||
self.index = {}
|
self.index = {}
|
||||||
|
self.block_entities = {}
|
||||||
from mosfet.info import blocks
|
from mosfet.info import blocks
|
||||||
self.indexed_ids = blocks.INDEXED_IDS
|
self.indexed_ids = blocks.INDEXED_IDS
|
||||||
self.center = None
|
self.center = None
|
||||||
@@ -242,6 +244,10 @@ class WorldState:
|
|||||||
chunk = ChunkColumn(packet.x, packet.z, packet.sections,
|
chunk = ChunkColumn(packet.x, packet.z, packet.sections,
|
||||||
self.min_section)
|
self.min_section)
|
||||||
self.chunks[(packet.x, packet.z)] = chunk
|
self.chunks[(packet.x, packet.z)] = chunk
|
||||||
|
for entity in getattr(packet, 'block_entities', []):
|
||||||
|
pos = (entity['x'], entity['y'], entity['z'])
|
||||||
|
self.set_block_entity(
|
||||||
|
pos, entity['type'], entity.get('data'))
|
||||||
for section_y, section in chunk.sections.items():
|
for section_y, section in chunk.sections.items():
|
||||||
palette = section['blocks']
|
palette = section['blocks']
|
||||||
if not self.indexed_ids.intersection(
|
if not self.indexed_ids.intersection(
|
||||||
@@ -275,6 +281,12 @@ class WorldState:
|
|||||||
return None
|
return None
|
||||||
return chunk.get_block_at(floor(x), floor(y), floor(z))
|
return chunk.get_block_at(floor(x), floor(y), floor(z))
|
||||||
|
|
||||||
|
def set_block_entity(self, position, entity_type, data):
|
||||||
|
self.block_entities[tuple(position)] = {
|
||||||
|
'type': entity_type,
|
||||||
|
'data': nbt_plain(data),
|
||||||
|
}
|
||||||
|
|
||||||
def set_block_at(self, x, y, z, block_state_id):
|
def set_block_at(self, x, y, z, block_state_id):
|
||||||
x, y, z = floor(x), floor(y), floor(z)
|
x, y, z = floor(x), floor(y), floor(z)
|
||||||
chunk = self.get_chunk(floor(x / 16), floor(z / 16))
|
chunk = self.get_chunk(floor(x / 16), floor(z / 16))
|
||||||
@@ -300,10 +312,16 @@ class WorldState:
|
|||||||
}
|
}
|
||||||
if not self.index[state_id]:
|
if not self.index[state_id]:
|
||||||
del self.index[state_id]
|
del self.index[state_id]
|
||||||
|
self.block_entities = {
|
||||||
|
pos: entity for pos, entity in self.block_entities.items()
|
||||||
|
if not (x0 <= pos[0] < x0 + 16 and
|
||||||
|
z0 <= pos[2] < z0 + 16)
|
||||||
|
}
|
||||||
|
|
||||||
def unload_all_chunks(self):
|
def unload_all_chunks(self):
|
||||||
self.chunks.clear()
|
self.chunks.clear()
|
||||||
self.index.clear()
|
self.index.clear()
|
||||||
|
self.block_entities.clear()
|
||||||
|
|
||||||
def get_loaded_area(self, ignore_empty=False):
|
def get_loaded_area(self, ignore_empty=False):
|
||||||
if not self.chunks:
|
if not self.chunks:
|
||||||
@@ -348,6 +366,7 @@ class EntityManager:
|
|||||||
for packet_type, handler in (
|
for packet_type, handler in (
|
||||||
(AddEntityPacket, self.handle_add_entity),
|
(AddEntityPacket, self.handle_add_entity),
|
||||||
(SetEntityDataPacket, self.handle_entity_data),
|
(SetEntityDataPacket, self.handle_entity_data),
|
||||||
|
(BlockEntityDataPacket, self.handle_block_entity_data),
|
||||||
(RemoveEntitiesPacket, self.handle_remove_entities),
|
(RemoveEntitiesPacket, self.handle_remove_entities),
|
||||||
(MoveEntityPositionPacket, self.handle_move),
|
(MoveEntityPositionPacket, self.handle_move),
|
||||||
(MoveEntityPositionRotationPacket, self.handle_move),
|
(MoveEntityPositionRotationPacket, self.handle_move),
|
||||||
@@ -379,6 +398,12 @@ class EntityManager:
|
|||||||
type=packet.entity_type_id, yaw=packet.yaw,
|
type=packet.entity_type_id, yaw=packet.yaw,
|
||||||
pitch=packet.pitch)
|
pitch=packet.pitch)
|
||||||
|
|
||||||
|
def handle_block_entity_data(self, packet):
|
||||||
|
location = packet.location
|
||||||
|
self.g.chunks.set_block_entity(
|
||||||
|
(location.x, location.y, location.z),
|
||||||
|
packet.block_entity_type, packet.data)
|
||||||
|
|
||||||
def handle_entity_data(self, packet):
|
def handle_entity_data(self, packet):
|
||||||
obj = self.g.objects.get(packet.entity_id)
|
obj = self.g.objects.get(packet.entity_id)
|
||||||
if obj is None or not packet.item_stacks:
|
if obj is None or not packet.item_stacks:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import collections
|
import collections
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
@@ -18,6 +19,63 @@ class World:
|
|||||||
def block_at(self, x, y, z):
|
def block_at(self, x, y, z):
|
||||||
return self.g.chunks.get_block_at(x, y, z)
|
return self.g.chunks.get_block_at(x, y, z)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sign_component_text(value):
|
||||||
|
if value is None:
|
||||||
|
return ''
|
||||||
|
if isinstance(value, str):
|
||||||
|
stripped = value.strip()
|
||||||
|
if stripped.startswith(('{', '[')):
|
||||||
|
try:
|
||||||
|
parsed = json.loads(stripped)
|
||||||
|
except ValueError:
|
||||||
|
return value
|
||||||
|
if parsed != value:
|
||||||
|
return World._sign_component_text(parsed)
|
||||||
|
return value
|
||||||
|
if isinstance(value, (list, tuple)):
|
||||||
|
return ''.join(World._sign_component_text(part) for part in value)
|
||||||
|
if isinstance(value, dict):
|
||||||
|
text = value.get('text', '')
|
||||||
|
if not text and value.get('translate'):
|
||||||
|
text = value['translate']
|
||||||
|
if isinstance(text, (dict, list)):
|
||||||
|
text = World._sign_component_text(text)
|
||||||
|
return (str(text) +
|
||||||
|
World._sign_component_text(value.get('with', [])) +
|
||||||
|
World._sign_component_text(value.get('extra', [])))
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
def find_signs(self, center, distance=16):
|
||||||
|
"""Return nearby loaded signs as position/side/four-lines records."""
|
||||||
|
result = []
|
||||||
|
for position, entity in self.g.chunks.block_entities.items():
|
||||||
|
if utils.phyp(center, position) > distance:
|
||||||
|
continue
|
||||||
|
data = entity.get('data') or {}
|
||||||
|
for side in ('front', 'back'):
|
||||||
|
text_data = data.get(side + '_text')
|
||||||
|
if not isinstance(text_data, dict):
|
||||||
|
continue
|
||||||
|
messages = text_data.get('messages', [])
|
||||||
|
lines = tuple(self._sign_component_text(line)
|
||||||
|
for line in messages[:4])
|
||||||
|
lines += ('',) * (4 - len(lines))
|
||||||
|
if any(line.strip() for line in lines):
|
||||||
|
result.append({
|
||||||
|
'position': position, 'side': side, 'lines': lines,
|
||||||
|
})
|
||||||
|
if not any(key in data for key in ('front_text', 'back_text')):
|
||||||
|
old_lines = tuple(self._sign_component_text(data.get(
|
||||||
|
'Text{}'.format(index), '')) for index in range(1, 5))
|
||||||
|
if any(line.strip() for line in old_lines):
|
||||||
|
result.append({
|
||||||
|
'position': position, 'side': 'front',
|
||||||
|
'lines': old_lines,
|
||||||
|
})
|
||||||
|
result.sort(key=lambda sign: utils.phyp(center, sign['position']))
|
||||||
|
return result
|
||||||
|
|
||||||
def check_air_column(self, pos, distance):
|
def check_air_column(self, pos, distance):
|
||||||
for i in range(distance):
|
for i in range(distance):
|
||||||
check = utils.padd(pos, (0, i, 0))
|
check = utils.padd(pos, (0, i, 0))
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import unittest
|
||||||
|
from munch import Munch
|
||||||
|
|
||||||
|
from mosfet.commands import Commands
|
||||||
|
from mosfet.protocol.managers import EntityManager, WorldState
|
||||||
|
from mosfet.world import World
|
||||||
|
|
||||||
|
|
||||||
|
class SignReadingTests(unittest.TestCase):
|
||||||
|
def test_world_decodes_front_and_back_sign_components(self):
|
||||||
|
chunks = Munch(block_entities={
|
||||||
|
(2, 64, 3): {
|
||||||
|
'type': 7,
|
||||||
|
'data': {
|
||||||
|
'front_text': {'messages': [
|
||||||
|
'{"text":"Hello","extra":[{"text":" world"}]}',
|
||||||
|
'{"text":"line two"}', '', ''],
|
||||||
|
},
|
||||||
|
'back_text': {'messages': [
|
||||||
|
'{"text":"back"}', '', '', ''],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
signs = World(Munch(chunks=chunks)).find_signs((0, 64, 0), 10)
|
||||||
|
|
||||||
|
self.assertEqual(signs, [
|
||||||
|
{'position': (2, 64, 3), 'side': 'front',
|
||||||
|
'lines': ('Hello world', 'line two', '', '')},
|
||||||
|
{'position': (2, 64, 3), 'side': 'back',
|
||||||
|
'lines': ('back', '', '', '')},
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_block_entity_update_is_cached_for_sign_search(self):
|
||||||
|
chunks = WorldState()
|
||||||
|
manager = object.__new__(EntityManager)
|
||||||
|
manager.g = Munch(chunks=chunks)
|
||||||
|
manager.handle_block_entity_data(Munch(
|
||||||
|
location=Munch(x=4, y=65, z=-2),
|
||||||
|
block_entity_type=7,
|
||||||
|
data={'front_text': {'messages': ['{"text":"Farm"}', '', '', '']}},
|
||||||
|
))
|
||||||
|
|
||||||
|
signs = World(Munch(chunks=chunks)).find_signs((4, 65, -2), 1)
|
||||||
|
self.assertEqual(signs[0]['lines'], ('Farm', '', '', ''))
|
||||||
|
|
||||||
|
def test_signs_command_replies_with_nearby_text(self):
|
||||||
|
class FakeChat:
|
||||||
|
def __init__(self):
|
||||||
|
self.sent = []
|
||||||
|
|
||||||
|
def set_handler(self, handler):
|
||||||
|
self.handler = handler
|
||||||
|
|
||||||
|
def send(self, message):
|
||||||
|
self.sent.append(message)
|
||||||
|
|
||||||
|
chat = FakeChat()
|
||||||
|
world = Munch(find_signs=lambda _position, _distance: [{
|
||||||
|
'position': (1, 64, 2), 'side': 'front',
|
||||||
|
'lines': ('Hello', 'world', '', ''),
|
||||||
|
}])
|
||||||
|
commands = Commands(Munch(
|
||||||
|
name='mosfet1', chat=chat, world=world, pos=(0.5, 64.0, 0.5)))
|
||||||
|
commands.handle_chat(('CHAT', 'tester', 'tester: !signs'))
|
||||||
|
|
||||||
|
self.assertEqual(chat.sent, ['front (1, 64, 2): Hello | world'])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user