Consolidate bot runtime on Minecraft 26.3
This commit is contained in:
@@ -1,102 +0,0 @@
|
||||
"""Entity and player-list updates for the 26.3 play protocol."""
|
||||
from munch import Munch
|
||||
|
||||
from minecraft.networking.modern import (
|
||||
AddEntityPacket, EntityPositionSyncPacket, MoveEntityPositionPacket,
|
||||
MoveEntityPositionRotationPacket, MoveEntityRotationPacket,
|
||||
PlayerInfoRemovePacket, PlayerInfoUpdatePacket, RemoveEntitiesPacket,
|
||||
)
|
||||
from mosfet.info import mobs
|
||||
|
||||
|
||||
class ModernEntityManager:
|
||||
def __init__(self, global_state):
|
||||
self.g = global_state
|
||||
self.g.mobs = self.g.get('mobs') or {}
|
||||
self.g.players = self.g.get('players') or {}
|
||||
self.g.objects = self.g.get('objects') or {}
|
||||
self.g.player_names = self.g.get('player_names') or {}
|
||||
register = self.g.connection.register_packet_listener
|
||||
for packet_type, handler in (
|
||||
(AddEntityPacket, self.handle_add_entity),
|
||||
(RemoveEntitiesPacket, self.handle_remove_entities),
|
||||
(MoveEntityPositionPacket, self.handle_move),
|
||||
(MoveEntityPositionRotationPacket, self.handle_move),
|
||||
(MoveEntityRotationPacket, self.handle_rotation),
|
||||
(EntityPositionSyncPacket, self.handle_position_sync),
|
||||
(PlayerInfoUpdatePacket, self.handle_player_info),
|
||||
(PlayerInfoRemovePacket, self.handle_player_info_remove),
|
||||
):
|
||||
register(handler, packet_type)
|
||||
|
||||
def handle_add_entity(self, packet):
|
||||
name = mobs.MOB_NAMES.get(packet.entity_type_id, 'unknown')
|
||||
common = dict(entity_id=packet.entity_id, x=packet.x,
|
||||
y=packet.y, z=packet.z)
|
||||
if name == 'player':
|
||||
player_uuid = str(packet.entity_uuid)
|
||||
self.g.players[packet.entity_id] = Munch(
|
||||
**common, player_uuid=player_uuid,
|
||||
yaw=packet.yaw, pitch=packet.pitch)
|
||||
elif name == 'item':
|
||||
self.g.objects[packet.entity_id] = Munch(
|
||||
**common, entity_uuid=str(packet.entity_uuid),
|
||||
velocity_x=packet.velocity_x * 8000,
|
||||
velocity_y=packet.velocity_y * 8000,
|
||||
velocity_z=packet.velocity_z * 8000)
|
||||
else:
|
||||
self.g.mobs[packet.entity_id] = Munch(
|
||||
**common, entity_uuid=str(packet.entity_uuid),
|
||||
type=packet.entity_type_id, yaw=packet.yaw,
|
||||
pitch=packet.pitch)
|
||||
|
||||
def _entity(self, entity_id):
|
||||
for collection in (self.g.players, self.g.mobs, self.g.objects):
|
||||
entity = collection.get(entity_id)
|
||||
if entity is not None:
|
||||
return entity
|
||||
return None
|
||||
|
||||
def handle_move(self, packet):
|
||||
entity = self._entity(packet.entity_id)
|
||||
if entity is None:
|
||||
return
|
||||
entity.x += packet.delta_x
|
||||
entity.y += packet.delta_y
|
||||
entity.z += packet.delta_z
|
||||
if hasattr(packet, 'yaw'):
|
||||
entity.yaw = packet.yaw
|
||||
entity.pitch = packet.pitch
|
||||
|
||||
def handle_rotation(self, packet):
|
||||
entity = self._entity(packet.entity_id)
|
||||
if entity is not None:
|
||||
entity.yaw = packet.yaw
|
||||
entity.pitch = packet.pitch
|
||||
|
||||
def handle_position_sync(self, packet):
|
||||
entity = self._entity(packet.entity_id)
|
||||
if entity is not None:
|
||||
entity.x, entity.y, entity.z = packet.x, packet.y, packet.z
|
||||
entity.yaw = packet.yaw
|
||||
entity.pitch = packet.pitch
|
||||
|
||||
def handle_remove_entities(self, packet):
|
||||
for entity_id in packet.entity_ids:
|
||||
self.g.players.pop(entity_id, None)
|
||||
self.g.mobs.pop(entity_id, None)
|
||||
self.g.objects.pop(entity_id, None)
|
||||
|
||||
def handle_player_info(self, packet):
|
||||
for profile_uuid, name in packet.entries:
|
||||
if name:
|
||||
uuid_text = str(profile_uuid)
|
||||
self.g.player_names[uuid_text] = name
|
||||
self.g.player_names[name] = profile_uuid
|
||||
self.g.player_names[name.lower()] = profile_uuid
|
||||
|
||||
def handle_player_info_remove(self, packet):
|
||||
removed = {str(uuid) for uuid in packet.uuids}
|
||||
for key, value in list(self.g.player_names.items()):
|
||||
if str(value) in removed or str(key) in removed:
|
||||
del self.g.player_names[key]
|
||||
@@ -1,55 +0,0 @@
|
||||
"""Minimal 26.3 inventory synchronization for legacy read-only commands."""
|
||||
from mosfet.protocol.types import Slot
|
||||
from minecraft.networking.modern import (
|
||||
ContainerSetContentPacket, ContainerSetSlotPacket, SetCursorItemPacket,
|
||||
)
|
||||
|
||||
|
||||
class ModernInventoryManager:
|
||||
def __init__(self, global_state):
|
||||
self.g = global_state
|
||||
self.g.inv = self.g.get('inv') or {}
|
||||
self.g.inventory_state_ids = self.g.get('inventory_state_ids') or {}
|
||||
self.g.inventory_partial = False
|
||||
self.g.cursor = Slot(present=False)
|
||||
register = self.g.connection.register_packet_listener
|
||||
register(self.handle_content, ContainerSetContentPacket)
|
||||
register(self.handle_slot, ContainerSetSlotPacket)
|
||||
register(self.handle_cursor, SetCursorItemPacket)
|
||||
|
||||
@staticmethod
|
||||
def to_slot(item):
|
||||
return Slot(item.present, item.item_id, item.item_count,
|
||||
components=item.components,
|
||||
component_ids=item.component_ids,
|
||||
removed_components=item.removed_components,
|
||||
components_unknown=item.components_unknown)
|
||||
|
||||
def handle_content(self, packet):
|
||||
slots = {index: self.to_slot(item)
|
||||
for index, item in enumerate(packet.items)}
|
||||
self.g.inventory_state_ids[packet.container_id] = packet.state_id
|
||||
if packet.container_id == 0:
|
||||
self.g.inv = slots
|
||||
self.g.inventory_partial = packet.partial
|
||||
self.g.cursor = self.to_slot(packet.carried_item)
|
||||
elif self.g.get('window'):
|
||||
self.g.window.contents = slots
|
||||
self.g.cursor = self.to_slot(packet.carried_item)
|
||||
|
||||
def handle_slot(self, packet):
|
||||
self.g.inventory_state_ids[packet.container_id] = packet.state_id
|
||||
item = self.to_slot(packet.item)
|
||||
if packet.item.components_unknown:
|
||||
self.g.inventory_partial = True
|
||||
if packet.container_id == 0:
|
||||
self.g.inv[packet.slot] = item
|
||||
elif packet.container_id == -1 and packet.slot == -1:
|
||||
self.g.cursor = item
|
||||
elif self.g.get('window'):
|
||||
self.g.window.contents[packet.slot] = item
|
||||
|
||||
def handle_cursor(self, packet):
|
||||
if packet.item.components_unknown:
|
||||
self.g.inventory_partial = True
|
||||
self.g.cursor = self.to_slot(packet.item)
|
||||
@@ -1,8 +1,18 @@
|
||||
import json
|
||||
from math import floor
|
||||
from munch import Munch
|
||||
|
||||
from minecraft.networking.packets import clientbound, serverbound
|
||||
from minecraft.networking.modern import (
|
||||
AddEntityPacket, ContainerSetContentPacket, ContainerSetSlotPacket,
|
||||
PaletteSection,
|
||||
EntityPositionSyncPacket, MoveEntityPositionPacket,
|
||||
MoveEntityPositionRotationPacket, MoveEntityRotationPacket,
|
||||
PlayerInfoRemovePacket, PlayerInfoUpdatePacket, RemoveEntitiesPacket,
|
||||
SetCursorItemPacket,
|
||||
)
|
||||
from mosfet.protocol import packets
|
||||
from mosfet.protocol.types import Slot
|
||||
from mosfet.info import blocks, mcdata, mobs
|
||||
|
||||
|
||||
@@ -172,3 +182,298 @@ class ChatManager:
|
||||
packet = serverbound.play.ChatPacket()
|
||||
packet.message = text
|
||||
self.g.connection.write_packet(packet)
|
||||
|
||||
|
||||
class ChunkColumn:
|
||||
def __init__(self, x, z, sections, min_section):
|
||||
self.x = x
|
||||
self.z = z
|
||||
self.sections = {
|
||||
min_section + offset: section
|
||||
for offset, section in enumerate(sections)
|
||||
}
|
||||
|
||||
def get_block_at(self, x, y, z):
|
||||
section = self.sections.get(y // 16)
|
||||
if section is None:
|
||||
return None
|
||||
index = ((y & 15) * 16 + (z & 15)) * 16 + (x & 15)
|
||||
return section['blocks'].get(index)
|
||||
|
||||
def set_block_at(self, x, y, z, block_state_id):
|
||||
section = self.sections.get(y // 16)
|
||||
if section is None:
|
||||
return None
|
||||
index = ((y & 15) * 16 + (z & 15)) * 16 + (x & 15)
|
||||
previous = section['blocks'].get(index)
|
||||
section['blocks'].set(index, block_state_id)
|
||||
return previous
|
||||
|
||||
|
||||
class WorldState:
|
||||
"""Sparse 26.3 chunk/block-state cache used by world and navigation."""
|
||||
def __init__(self, min_y=-64):
|
||||
self.min_y = min_y
|
||||
self.min_section = min_y // 16
|
||||
self.chunks = {}
|
||||
self.index = {}
|
||||
from mosfet.info import blocks
|
||||
self.indexed_ids = blocks.INDEXED_IDS
|
||||
self.center = None
|
||||
self.radius = None
|
||||
self.dimension = 'minecraft:overworld'
|
||||
self.player_id = None
|
||||
|
||||
def set_dimension(self, dimension, dimension_type=None):
|
||||
if dimension:
|
||||
self.dimension = dimension
|
||||
if dimension_type:
|
||||
min_y = dimension_type.get('min_y', self.min_y)
|
||||
if isinstance(min_y, int):
|
||||
self.min_y = min_y
|
||||
self.min_section = min_y // 16
|
||||
|
||||
def load_chunk(self, packet):
|
||||
# Refresh selectors in case block metadata was reloaded before login.
|
||||
from mosfet.info import blocks
|
||||
self.indexed_ids = blocks.INDEXED_IDS
|
||||
self.unload_chunk(packet.x, packet.z)
|
||||
chunk = ChunkColumn(packet.x, packet.z, packet.sections,
|
||||
self.min_section)
|
||||
self.chunks[(packet.x, packet.z)] = chunk
|
||||
for section_y, section in chunk.sections.items():
|
||||
palette = section['blocks']
|
||||
if not self.indexed_ids.intersection(
|
||||
palette.palette or [palette.get(i) for i in range(palette.size)]):
|
||||
continue
|
||||
for index in range(palette.size):
|
||||
state_id = palette.get(index)
|
||||
if state_id not in self.indexed_ids:
|
||||
continue
|
||||
local_x = index & 0xF
|
||||
local_z = (index >> 4) & 0xF
|
||||
local_y = (index >> 8) & 0xF
|
||||
pos = (packet.x * 16 + local_x,
|
||||
section_y * 16 + local_y,
|
||||
packet.z * 16 + local_z)
|
||||
self.index.setdefault(state_id, set()).add(pos)
|
||||
|
||||
def handle_block_update(self, packet):
|
||||
self.set_block_at(packet.x, packet.y, packet.z, packet.block_state_id)
|
||||
|
||||
def handle_section_update(self, packet):
|
||||
for x, y, z, state_id in packet.changes:
|
||||
self.set_block_at(x, y, z, state_id)
|
||||
|
||||
def get_chunk(self, x, z):
|
||||
return self.chunks.get((x, z))
|
||||
|
||||
def get_block_at(self, x, y, z):
|
||||
chunk = self.get_chunk(floor(x / 16), floor(z / 16))
|
||||
if chunk is None:
|
||||
return None
|
||||
return chunk.get_block_at(floor(x), floor(y), floor(z))
|
||||
|
||||
def set_block_at(self, x, y, z, block_state_id):
|
||||
x, y, z = floor(x), floor(y), floor(z)
|
||||
chunk = self.get_chunk(floor(x / 16), floor(z / 16))
|
||||
if chunk is None:
|
||||
return False
|
||||
previous = chunk.set_block_at(x, y, z, block_state_id)
|
||||
pos = (x, y, z)
|
||||
if previous in self.indexed_ids:
|
||||
self.index.get(previous, set()).discard(pos)
|
||||
if block_state_id in self.indexed_ids:
|
||||
self.index.setdefault(block_state_id, set()).add(pos)
|
||||
return True
|
||||
|
||||
def unload_chunk(self, x, z):
|
||||
chunk = self.chunks.pop((x, z), None)
|
||||
if chunk is None:
|
||||
return
|
||||
x0, z0 = x * 16, z * 16
|
||||
for state_id, locations in list(self.index.items()):
|
||||
self.index[state_id] = {
|
||||
pos for pos in locations
|
||||
if not (x0 <= pos[0] < x0 + 16 and z0 <= pos[2] < z0 + 16)
|
||||
}
|
||||
if not self.index[state_id]:
|
||||
del self.index[state_id]
|
||||
|
||||
def unload_all_chunks(self):
|
||||
self.chunks.clear()
|
||||
self.index.clear()
|
||||
|
||||
def get_loaded_area(self, ignore_empty=False):
|
||||
if not self.chunks:
|
||||
return None
|
||||
xs = [pos[0] for pos in self.chunks]
|
||||
zs = [pos[1] for pos in self.chunks]
|
||||
ys = [section_y for chunk in self.chunks.values()
|
||||
for section_y in chunk.sections]
|
||||
return ((min(xs), min(ys), min(zs)),
|
||||
(max(xs), max(ys), max(zs)))
|
||||
|
||||
def check_loaded(self, chunk_distance=None):
|
||||
if self.center is None:
|
||||
return bool(self.chunks)
|
||||
radius = self.radius if chunk_distance is None else chunk_distance
|
||||
if radius is None:
|
||||
return bool(self.chunks)
|
||||
cx, cz = self.center
|
||||
return all((x, z) in self.chunks
|
||||
for x in range(cx - radius, cx + radius + 1)
|
||||
for z in range(cz - radius, cz + radius + 1))
|
||||
|
||||
@property
|
||||
def loaded_chunk_count(self):
|
||||
return len(self.chunks)
|
||||
|
||||
def __repr__(self):
|
||||
return ('WorldState(dimension={!r}, chunks={}, min_y={}, center={!r}, '
|
||||
'radius={!r})').format(
|
||||
self.dimension, len(self.chunks), self.min_y,
|
||||
self.center, self.radius)
|
||||
|
||||
|
||||
class EntityManager:
|
||||
def __init__(self, global_state):
|
||||
self.g = global_state
|
||||
self.g.mobs = self.g.get('mobs') or {}
|
||||
self.g.players = self.g.get('players') or {}
|
||||
self.g.objects = self.g.get('objects') or {}
|
||||
self.g.player_names = self.g.get('player_names') or {}
|
||||
register = self.g.connection.register_packet_listener
|
||||
for packet_type, handler in (
|
||||
(AddEntityPacket, self.handle_add_entity),
|
||||
(RemoveEntitiesPacket, self.handle_remove_entities),
|
||||
(MoveEntityPositionPacket, self.handle_move),
|
||||
(MoveEntityPositionRotationPacket, self.handle_move),
|
||||
(MoveEntityRotationPacket, self.handle_rotation),
|
||||
(EntityPositionSyncPacket, self.handle_position_sync),
|
||||
(PlayerInfoUpdatePacket, self.handle_player_info),
|
||||
(PlayerInfoRemovePacket, self.handle_player_info_remove),
|
||||
):
|
||||
register(handler, packet_type)
|
||||
|
||||
def handle_add_entity(self, packet):
|
||||
name = mobs.MOB_NAMES.get(packet.entity_type_id, 'unknown')
|
||||
common = dict(entity_id=packet.entity_id, x=packet.x,
|
||||
y=packet.y, z=packet.z)
|
||||
if name == 'player':
|
||||
player_uuid = str(packet.entity_uuid)
|
||||
self.g.players[packet.entity_id] = Munch(
|
||||
**common, player_uuid=player_uuid,
|
||||
yaw=packet.yaw, pitch=packet.pitch)
|
||||
elif name == 'item':
|
||||
self.g.objects[packet.entity_id] = Munch(
|
||||
**common, entity_uuid=str(packet.entity_uuid),
|
||||
velocity_x=packet.velocity_x * 8000,
|
||||
velocity_y=packet.velocity_y * 8000,
|
||||
velocity_z=packet.velocity_z * 8000)
|
||||
else:
|
||||
self.g.mobs[packet.entity_id] = Munch(
|
||||
**common, entity_uuid=str(packet.entity_uuid),
|
||||
type=packet.entity_type_id, yaw=packet.yaw,
|
||||
pitch=packet.pitch)
|
||||
|
||||
def _entity(self, entity_id):
|
||||
for collection in (self.g.players, self.g.mobs, self.g.objects):
|
||||
entity = collection.get(entity_id)
|
||||
if entity is not None:
|
||||
return entity
|
||||
return None
|
||||
|
||||
def handle_move(self, packet):
|
||||
entity = self._entity(packet.entity_id)
|
||||
if entity is None:
|
||||
return
|
||||
entity.x += packet.delta_x
|
||||
entity.y += packet.delta_y
|
||||
entity.z += packet.delta_z
|
||||
if hasattr(packet, 'yaw'):
|
||||
entity.yaw = packet.yaw
|
||||
entity.pitch = packet.pitch
|
||||
|
||||
def handle_rotation(self, packet):
|
||||
entity = self._entity(packet.entity_id)
|
||||
if entity is not None:
|
||||
entity.yaw = packet.yaw
|
||||
entity.pitch = packet.pitch
|
||||
|
||||
def handle_position_sync(self, packet):
|
||||
entity = self._entity(packet.entity_id)
|
||||
if entity is not None:
|
||||
entity.x, entity.y, entity.z = packet.x, packet.y, packet.z
|
||||
entity.yaw = packet.yaw
|
||||
entity.pitch = packet.pitch
|
||||
|
||||
def handle_remove_entities(self, packet):
|
||||
for entity_id in packet.entity_ids:
|
||||
self.g.players.pop(entity_id, None)
|
||||
self.g.mobs.pop(entity_id, None)
|
||||
self.g.objects.pop(entity_id, None)
|
||||
|
||||
def handle_player_info(self, packet):
|
||||
for profile_uuid, name in packet.entries:
|
||||
if name:
|
||||
uuid_text = str(profile_uuid)
|
||||
self.g.player_names[uuid_text] = name
|
||||
self.g.player_names[name] = profile_uuid
|
||||
self.g.player_names[name.lower()] = profile_uuid
|
||||
|
||||
def handle_player_info_remove(self, packet):
|
||||
removed = {str(uuid) for uuid in packet.uuids}
|
||||
for key, value in list(self.g.player_names.items()):
|
||||
if str(value) in removed or str(key) in removed:
|
||||
del self.g.player_names[key]
|
||||
|
||||
|
||||
class InventoryManager:
|
||||
def __init__(self, global_state):
|
||||
self.g = global_state
|
||||
self.g.inv = self.g.get('inv') or {}
|
||||
self.g.inventory_state_ids = self.g.get('inventory_state_ids') or {}
|
||||
self.g.inventory_partial = False
|
||||
self.g.cursor = Slot(present=False)
|
||||
register = self.g.connection.register_packet_listener
|
||||
register(self.handle_content, ContainerSetContentPacket)
|
||||
register(self.handle_slot, ContainerSetSlotPacket)
|
||||
register(self.handle_cursor, SetCursorItemPacket)
|
||||
|
||||
@staticmethod
|
||||
def to_slot(item):
|
||||
return Slot(item.present, item.item_id, item.item_count,
|
||||
components=item.components,
|
||||
component_ids=item.component_ids,
|
||||
removed_components=item.removed_components,
|
||||
components_unknown=item.components_unknown)
|
||||
|
||||
def handle_content(self, packet):
|
||||
slots = {index: self.to_slot(item)
|
||||
for index, item in enumerate(packet.items)}
|
||||
self.g.inventory_state_ids[packet.container_id] = packet.state_id
|
||||
if packet.container_id == 0:
|
||||
self.g.inv = slots
|
||||
self.g.inventory_partial = packet.partial
|
||||
self.g.cursor = self.to_slot(packet.carried_item)
|
||||
elif self.g.get('window'):
|
||||
self.g.window.contents = slots
|
||||
self.g.cursor = self.to_slot(packet.carried_item)
|
||||
|
||||
def handle_slot(self, packet):
|
||||
self.g.inventory_state_ids[packet.container_id] = packet.state_id
|
||||
item = self.to_slot(packet.item)
|
||||
if packet.item.components_unknown:
|
||||
self.g.inventory_partial = True
|
||||
if packet.container_id == 0:
|
||||
self.g.inv[packet.slot] = item
|
||||
elif packet.container_id == -1 and packet.slot == -1:
|
||||
self.g.cursor = item
|
||||
elif self.g.get('window'):
|
||||
self.g.window.contents[packet.slot] = item
|
||||
|
||||
def handle_cursor(self, packet):
|
||||
if packet.item.components_unknown:
|
||||
self.g.inventory_partial = True
|
||||
self.g.cursor = self.to_slot(packet.item)
|
||||
|
||||
Reference in New Issue
Block a user