Begin port to 26.3: movement
This commit is contained in:
@@ -18,7 +18,7 @@ install the connection-mode dependencies:
|
||||
```
|
||||
$ python3 -m venv .venv
|
||||
$ source .venv/bin/activate
|
||||
$ python -m pip install flask flask-cors munch watchdog -e ../pyCraft
|
||||
$ python -m pip install astar flask flask-cors munch watchdog -e ../pyCraft
|
||||
```
|
||||
|
||||
## Running
|
||||
@@ -26,15 +26,20 @@ $ python -m pip install flask flask-cors munch watchdog -e ../pyCraft
|
||||
Run the bot directly with Python. For the Protospace offline-mode server:
|
||||
|
||||
```
|
||||
$ SERVER=games.protospace.ca python mosfet.py
|
||||
$ SERVER=127.0.0.1 python mosfet.py
|
||||
```
|
||||
|
||||
`mosfet.py` defaults to that server, player name `mosfet1`, and the current
|
||||
AuthMe password. Override `SERVER`, `PORT`, `PLAYER_NAME`, or `GAME_PASSWORD`
|
||||
through environment variables as needed. Microsoft authentication is not used.
|
||||
|
||||
The current entry point connects and handles AuthMe, but world automation is
|
||||
not enabled yet: those modules still depend on 1.16-era packet and world data.
|
||||
The 26.3 client now handles AuthMe, chat commands, chunk/world-state caching,
|
||||
modern block/item/entity data, basic entity tracking, and path-following movement
|
||||
(`1goto x y z`).
|
||||
Inventory, entity tracking, block breaking/placing, and the farming/trading jobs
|
||||
remain disabled until their 26.3 packets are migrated. The default server is
|
||||
`127.0.0.1`; set `SERVER` explicitly only when you intentionally want another
|
||||
server.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -54,11 +59,11 @@ would be ran like `1farm wood` or `1pos`.
|
||||
The exception are the below public commands, they can optionally be prefixed with `!`
|
||||
and all bots will run the command.
|
||||
|
||||
Commands can be issued in public chat or private message like `/msg mosfet1
|
||||
1farm wood`. The bot will reply the same way the command was issued.
|
||||
|
||||
Use `echo` to make Mosfet run Minecraft commands. For example, `1echo /sethome
|
||||
home` will cause him to issue the `/sethome` command.
|
||||
The current 26.3 command path handles public chat. Private-message routing is
|
||||
not migrated yet. Verified commands are `!ping`, `!pos`, `!loaded`, `!info x y
|
||||
z`, and addressed path movement such as `1goto x y z`. The command catalog below
|
||||
is retained from the legacy bot; inventory, interaction, and farming/trading
|
||||
commands are not yet available on 26.3.
|
||||
|
||||
### Public Commands
|
||||
|
||||
|
||||
@@ -62,10 +62,11 @@ def root():
|
||||
return app.send_static_file('index.html')
|
||||
|
||||
def main():
|
||||
"""Connect using the current offline/AuthMe protocol bootstrap.
|
||||
"""Connect with offline/AuthMe and enable only the migrated 26.3 features.
|
||||
|
||||
The legacy automation loop is deliberately not started yet: its packet
|
||||
handlers still target Minecraft 1.16 and will be migrated incrementally.
|
||||
Current gameplay support is read-only world/entity state plus path
|
||||
following; inventory, interaction, and legacy job packet handlers remain
|
||||
disabled until migrated.
|
||||
"""
|
||||
reload_timeout = time.time()
|
||||
reload_requested = False
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Safe, movement-only bridge from legacy navigation to protocol 777."""
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
|
||||
def start(global_state):
|
||||
"""Start the legacy path-following physics, without legacy job/game packets."""
|
||||
g = global_state
|
||||
g.protocol_version = 777
|
||||
g.time = 0.0
|
||||
g.path = []
|
||||
g.look_at = None
|
||||
g.y_v = 0.0
|
||||
g.y_a = 0.0
|
||||
g.crawling = False
|
||||
g.afk_timeout = 0.0
|
||||
g.objects = g.get('objects') or {}
|
||||
g.players = g.get('players') or {}
|
||||
g.breaking = None
|
||||
g.break_time = 0.0
|
||||
g.correction_count = g.get('correction_count', 0)
|
||||
g.yaw = float(g.get('yaw', 0.0) or 0.0)
|
||||
g.pitch = float(g.get('pitch', 0.0) or 0.0)
|
||||
g.chunks.loading = False
|
||||
# Local navigation only needs nearby chunks; waiting for the entire view
|
||||
# radius can stall on servers that stream chunks gradually.
|
||||
g.physics_render_distance = 2
|
||||
g.movement_paused = False
|
||||
g.movement_status = 'idle'
|
||||
g.movement_error = None
|
||||
# Inventory, interaction, and job packets remain disabled until migrated.
|
||||
g.job = None
|
||||
g.game = None
|
||||
|
||||
def run():
|
||||
from mosfet import bot, utils
|
||||
loaded_since = None
|
||||
|
||||
while (g.get('running', True) and g.get('connection') and
|
||||
g.connection.connected):
|
||||
started = time.monotonic()
|
||||
now = time.monotonic()
|
||||
if g.chunks.check_loaded(g.physics_render_distance):
|
||||
if loaded_since is None:
|
||||
loaded_since = now
|
||||
else:
|
||||
loaded_since = None
|
||||
# Let the initial position correction and first chunk batch settle
|
||||
# before allowing any client-originated movement.
|
||||
movement_ready = loaded_since is not None and now - loaded_since >= 0.5
|
||||
if g.get('movement_paused'):
|
||||
g.movement_status = 'paused'
|
||||
elif g.path and not movement_ready:
|
||||
g.movement_status = 'waiting_for_chunks'
|
||||
elif g.path and movement_ready:
|
||||
g.movement_status = 'following_path'
|
||||
try:
|
||||
bot.tick(g)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
g.movement_error = traceback.format_exc()
|
||||
g.path = []
|
||||
g.movement_status = 'error'
|
||||
print('[26.3 movement] stopped after navigation error',
|
||||
flush=True)
|
||||
return
|
||||
elif not g.path and not g.get('movement_paused'):
|
||||
g.movement_status = 'idle'
|
||||
delay = max(0.0, utils.TICK - (time.monotonic() - started))
|
||||
time.sleep(delay)
|
||||
|
||||
worker = threading.Thread(target=run, name='mosfet-26.3-movement',
|
||||
daemon=True)
|
||||
g.movement_thread = worker
|
||||
worker.start()
|
||||
return worker
|
||||
+31
-3
@@ -65,12 +65,23 @@ def tick(global_state):
|
||||
target = None
|
||||
|
||||
# make sure current chunks are loaded for physics
|
||||
if not g.chunks.check_loaded(g.info.render_distance):
|
||||
required_distance = getattr(g, 'physics_render_distance',
|
||||
g.info.render_distance)
|
||||
if not g.chunks.check_loaded(required_distance):
|
||||
if not g.chunks.loading:
|
||||
print('Loading chunks', end='', flush=True)
|
||||
g.chunks.loading = time.time()
|
||||
packet = serverbound.play.PositionAndLookPacket(x=p.x, feet_y=p.y, z=p.z, pitch=0, yaw=0, on_ground=True)
|
||||
if getattr(g, 'protocol_version', 0) == 777:
|
||||
from minecraft.networking.modern import PlayerPositionRotation
|
||||
packet = PlayerPositionRotation(
|
||||
x=p.x, y=p.y, z=p.z, pitch=0, yaw=0, on_ground=True)
|
||||
else:
|
||||
packet = serverbound.play.PositionAndLookPacket(
|
||||
x=p.x, feet_y=p.y, z=p.z, pitch=0, yaw=0, on_ground=True)
|
||||
g.connection.write_packet(packet, force=True)
|
||||
if getattr(g, 'protocol_version', 0) == 777:
|
||||
from minecraft.networking.modern import ClientTickEnd
|
||||
g.connection.write_packet(ClientTickEnd(), force=True)
|
||||
return
|
||||
else:
|
||||
if g.chunks.loading:
|
||||
@@ -197,10 +208,27 @@ def tick(global_state):
|
||||
target_pitch_d = 0 - g.pitch
|
||||
g.pitch += utils.cap(target_pitch_d, 10)
|
||||
|
||||
packet = serverbound.play.PositionAndLookPacket(x=p.x, feet_y=p.y, z=p.z, pitch=g.pitch, yaw=g.yaw, on_ground=(not in_air))
|
||||
if getattr(g, 'protocol_version', 0) == 777:
|
||||
from minecraft.networking.modern import PlayerPositionRotation
|
||||
packet = PlayerPositionRotation(
|
||||
x=p.x, y=p.y, z=p.z, pitch=g.pitch, yaw=g.yaw,
|
||||
on_ground=(not in_air))
|
||||
else:
|
||||
packet = serverbound.play.PositionAndLookPacket(
|
||||
x=p.x, feet_y=p.y, z=p.z, pitch=g.pitch, yaw=g.yaw,
|
||||
on_ground=(not in_air))
|
||||
if getattr(g, 'protocol_version', 0) == 777:
|
||||
from minecraft.networking.modern import ClientTickEnd
|
||||
# Keep the client-tick boundary after its movement packet on the wire;
|
||||
# force=True on only the latter could overtake the queued position.
|
||||
g.connection.write_packet(packet, force=True)
|
||||
g.connection.write_packet(ClientTickEnd(), force=True)
|
||||
else:
|
||||
g.connection.write_packet(packet)
|
||||
|
||||
if g.job:
|
||||
g.job.tick()
|
||||
if g.game:
|
||||
g.game.tick() # order important for correction_count
|
||||
|
||||
|
||||
|
||||
+7
-2
@@ -39,7 +39,9 @@ class Commands:
|
||||
|
||||
text = text.replace('zzz', '!zzz')
|
||||
|
||||
match = re.match(r'(.*\W+)\s+(['+bot_num+'|!])(\w+) ?(.*)', text)
|
||||
match = re.match(
|
||||
r'(.*\W+)\s+([{}!])(\w+) ?(.*)'.format(re.escape(bot_num)),
|
||||
text)
|
||||
if match:
|
||||
meta, prefix, command, data = match.groups()
|
||||
else:
|
||||
@@ -155,7 +157,10 @@ class Commands:
|
||||
if command == 'loaded':
|
||||
l = self.g.chunks.get_loaded_area()
|
||||
reply = '{}, {}, {} to {}, {}, {}'.format(*l[0], *l[1])
|
||||
reply += ' - ' + str(len(self.g.chunks.chunks)//16) + ' chunks'
|
||||
chunk_count = getattr(
|
||||
self.g.chunks, 'loaded_chunk_count',
|
||||
len(self.g.chunks.chunks) // 16)
|
||||
reply += ' - ' + str(chunk_count) + ' chunks'
|
||||
|
||||
## !players - prints the current players
|
||||
## !players clear - clears the current player list
|
||||
|
||||
+49
-6
@@ -3,11 +3,14 @@
|
||||
Run with SERVER=... PLAYER_NAME=mosfet1 GAME_PASSWORD=... python -m mosfet.connect
|
||||
"""
|
||||
import os
|
||||
import threading
|
||||
from minecraft.networking.connection import Connection
|
||||
from minecraft.networking.modern import (
|
||||
CancelPostjoinDialog, ChatCommand, PlayShowDialog,
|
||||
PlayLogin, SubmitPostjoinLoginDialog, SystemChat, TeleportPosition,
|
||||
)
|
||||
from mosfet.world_26_3 import WorldState
|
||||
from mosfet.vector import Point3D
|
||||
|
||||
|
||||
def component_text(value):
|
||||
@@ -39,14 +42,17 @@ def component_text(value):
|
||||
|
||||
|
||||
def main(global_state=None):
|
||||
server = os.environ.get('SERVER', 'games.protospace.ca')
|
||||
server = os.environ.get('SERVER', '127.0.0.1')
|
||||
port = int(os.environ.get('PORT', '25565'))
|
||||
username = os.environ.get('PLAYER_NAME', 'mosfet1')
|
||||
password = os.environ.get('GAME_PASSWORD', 'mosfetbot')
|
||||
|
||||
connection = Connection(server, port, username=username,
|
||||
allowed_versions=['26.3'])
|
||||
allowed_versions=['26.3'],
|
||||
handle_exception=False)
|
||||
connection.authme_password = password
|
||||
connection.world_state = WorldState()
|
||||
connection.dimension_types_by_id = []
|
||||
sent_auth_commands = set()
|
||||
if global_state is not None:
|
||||
global_state.connection = connection
|
||||
@@ -54,12 +60,32 @@ def main(global_state=None):
|
||||
global_state.connected = False
|
||||
global_state.connection_status = 'connecting'
|
||||
global_state.player_position = None
|
||||
global_state.world_state = connection.world_state
|
||||
global_state.chunks = connection.world_state
|
||||
from mosfet.protocol.managers import DataManager
|
||||
global_state.mcdata = DataManager()
|
||||
|
||||
@connection.listener(PlayLogin)
|
||||
def on_play_login(packet):
|
||||
if global_state is not None:
|
||||
global_state.connected = True
|
||||
global_state.connection_status = 'in_game'
|
||||
global_state.player_id = packet.player_id
|
||||
global_state.dimension = packet.dimension.removeprefix('minecraft:')
|
||||
packet.render_distance = packet.chunk_radius
|
||||
global_state.info = packet
|
||||
global_state.eid = packet.player_id
|
||||
global_state.command_lock = False
|
||||
from mosfet.world import World
|
||||
from mosfet.commands import Commands
|
||||
from mosfet.protocol.modern import ModernChatManager
|
||||
from mosfet.protocol.entities_26_3 import ModernEntityManager
|
||||
from mosfet.automation_26_3 import start as start_modern_movement
|
||||
global_state.world = World(global_state)
|
||||
global_state.chat = ModernChatManager(global_state)
|
||||
global_state.entities = ModernEntityManager(global_state)
|
||||
global_state.commands = Commands(global_state)
|
||||
start_modern_movement(global_state)
|
||||
|
||||
|
||||
def send_auth_command(action):
|
||||
@@ -75,7 +101,15 @@ def main(global_state=None):
|
||||
@connection.listener(TeleportPosition)
|
||||
def on_spawn(packet):
|
||||
if global_state is not None:
|
||||
global_state.player_position = (packet.x, packet.y, packet.z)
|
||||
position = getattr(connection, 'player_position', None)
|
||||
if position is None:
|
||||
position = (packet.x, packet.y, packet.z,
|
||||
packet.yaw, packet.pitch)
|
||||
x, y, z, yaw, pitch = position
|
||||
global_state.player_position = (x, y, z)
|
||||
global_state.pos = Point3D(global_state.player_position)
|
||||
global_state.yaw = yaw
|
||||
global_state.pitch = pitch
|
||||
if getattr(connection, 'authme_mode', 'register') == 'register':
|
||||
send_auth_command('register')
|
||||
|
||||
@@ -96,6 +130,9 @@ def main(global_state=None):
|
||||
message = component_text(packet.content)
|
||||
if global_state is not None:
|
||||
global_state.last_chat = message
|
||||
chat = global_state.get('chat')
|
||||
if chat is not None:
|
||||
chat.handle_system_chat(packet, message)
|
||||
print('[server] ' + message, flush=True)
|
||||
lowered = message.lower()
|
||||
if 'already registered' in lowered:
|
||||
@@ -111,26 +148,32 @@ def main(global_state=None):
|
||||
connection.connect()
|
||||
print('Connected as {} to {}:{} using offline protocol 777.'.format(
|
||||
username, server, port), flush=True)
|
||||
shutdown_requested = False
|
||||
try:
|
||||
while connection.connected:
|
||||
if global_state is not None and not global_state.get('running', True):
|
||||
shutdown_requested = True
|
||||
break
|
||||
thread = connection.networking_thread
|
||||
if thread is None:
|
||||
break
|
||||
thread.join(timeout=0.5)
|
||||
if connection.connected:
|
||||
connection.disconnect()
|
||||
if connection.exception:
|
||||
connection.disconnect(immediate=True)
|
||||
if connection.exception and not shutdown_requested:
|
||||
raise connection.exception
|
||||
print('Connection ended.', flush=True)
|
||||
except KeyboardInterrupt:
|
||||
shutdown_requested = True
|
||||
print('Disconnecting...', flush=True)
|
||||
connection.disconnect()
|
||||
finally:
|
||||
if connection.connected:
|
||||
connection.disconnect()
|
||||
connection.disconnect(immediate=True)
|
||||
if global_state is not None:
|
||||
worker = global_state.get('movement_thread')
|
||||
if worker and worker is not threading.current_thread():
|
||||
worker.join(timeout=1.0)
|
||||
global_state.connection = False
|
||||
global_state.connected = False
|
||||
global_state.connection_status = 'disconnected'
|
||||
|
||||
+84
-273
@@ -1,280 +1,84 @@
|
||||
import json
|
||||
import importlib
|
||||
"""Minecraft 26.3 block-state metadata and legacy automation selectors."""
|
||||
from itertools import product
|
||||
|
||||
from mosfet.info import mcdata
|
||||
|
||||
MCD_BLOCKS = {}
|
||||
for d in mcdata.mcd.blocks.values():
|
||||
MCD_BLOCKS[d['name']] = d
|
||||
|
||||
with open('minecraft_data/blocks.json') as f:
|
||||
JSON_BLOCKS = json.load(f)
|
||||
MCD_BLOCKS = mcdata.mcd.blocks
|
||||
JSON_BLOCKS = {'minecraft:' + name: data
|
||||
for name, data in MCD_BLOCKS.items()}
|
||||
|
||||
BLOCKS = {}
|
||||
for name, data in JSON_BLOCKS.items():
|
||||
for state in data['states']:
|
||||
BLOCKS[state['id']] = name.replace('minecraft:', '')
|
||||
|
||||
PROPS = {}
|
||||
for name, data in JSON_BLOCKS.items():
|
||||
for state in data['states']:
|
||||
PROPS[state['id']] = state.get('properties', {})
|
||||
COLLISION_BOXES = {}
|
||||
|
||||
for name, data in MCD_BLOCKS.items():
|
||||
first = data['minStateId']
|
||||
last = data['maxStateId']
|
||||
properties = data.get('states', [])
|
||||
property_values = [prop.get('values', []) for prop in properties]
|
||||
combinations = list(product(*property_values)) if property_values else [()]
|
||||
for offset, state_id in enumerate(range(first, last + 1)):
|
||||
BLOCKS[state_id] = name
|
||||
if offset < len(combinations):
|
||||
PROPS[state_id] = {
|
||||
prop['name']: value
|
||||
for prop, value in zip(properties, combinations[offset])
|
||||
}
|
||||
else:
|
||||
PROPS[state_id] = {}
|
||||
|
||||
shape_ref = mcdata.mcd.collision_shapes['blocks'].get(name, 1)
|
||||
refs = shape_ref if isinstance(shape_ref, list) else [shape_ref] * (last-first+1)
|
||||
for offset, state_id in enumerate(range(first, last + 1)):
|
||||
ref = refs[offset] if offset < len(refs) else refs[-1]
|
||||
COLLISION_BOXES[state_id] = mcdata.mcd.collision_shapes['shapes'].get(
|
||||
str(ref), [])
|
||||
|
||||
BREAK_DISTANCE = 6
|
||||
|
||||
AIR = 0
|
||||
STONE = 1
|
||||
SAND = 66
|
||||
SINGLE_SNOW = 3921
|
||||
SOUL_TORCH = 4008
|
||||
EMERALD_BLOCK = 5407
|
||||
|
||||
AIR = MCD_BLOCKS['air']['minStateId']
|
||||
STONE = MCD_BLOCKS['stone']['minStateId']
|
||||
SAND = MCD_BLOCKS['sand']['minStateId']
|
||||
SOUL_TORCH = MCD_BLOCKS['soul_torch']['minStateId']
|
||||
EMERALD_BLOCK = MCD_BLOCKS['emerald_block']['minStateId']
|
||||
SINGLE_SNOW = MCD_BLOCKS.get('snow', MCD_BLOCKS.get('snow_layer', {})).get(
|
||||
'minStateId', AIR)
|
||||
TEST_BLOCK = (616, 78, 496)
|
||||
|
||||
|
||||
AVOID = [
|
||||
'lava',
|
||||
'water',
|
||||
'fire',
|
||||
'magma_block',
|
||||
'oak_fence',
|
||||
'oak_fence_gate',
|
||||
'nether_brick_fence',
|
||||
'spruce_fence_gate',
|
||||
'birch_fence_gate',
|
||||
'jungle_fence_gate',
|
||||
'acacia_fence_gate',
|
||||
'dark_oak_fence_gate',
|
||||
'spruce_fence',
|
||||
'birch_fence',
|
||||
'jungle_fence',
|
||||
'acacia_fence',
|
||||
'dark_oak_fence',
|
||||
'sweet_berry_bush',
|
||||
'nether_portal',
|
||||
'end_portal',
|
||||
'cobblestone_wall',
|
||||
'mossy_cobblestone_wall',
|
||||
'brick_wall',
|
||||
'prismarine_wall',
|
||||
'red_sandstone_wall',
|
||||
'mossy_stone_brick_wall',
|
||||
'granite_wall',
|
||||
'stone_brick_wall',
|
||||
'nether_brick_wall',
|
||||
'andesite_wall',
|
||||
'red_nether_brick_wall',
|
||||
'sandstone_wall',
|
||||
'end_stone_brick_wall',
|
||||
'diorite_wall',
|
||||
'oak_sapling', # saplings can grow up and hurt
|
||||
'spruce_sapling',
|
||||
'birch_sapling',
|
||||
'jungle_sapling',
|
||||
'acacia_sapling',
|
||||
'dark_oak_sapling',
|
||||
]
|
||||
|
||||
NON_SOLID = [
|
||||
'air',
|
||||
'powered_rail',
|
||||
'detector_rail',
|
||||
'grass',
|
||||
'fern',
|
||||
'dead_bush',
|
||||
'seagrass',
|
||||
'tall_seagrass',
|
||||
'dandelion',
|
||||
'poppy',
|
||||
'blue_orchid',
|
||||
'allium',
|
||||
'azure_bluet',
|
||||
'red_tulip',
|
||||
'orange_tulip',
|
||||
'white_tulip',
|
||||
'pink_tulip',
|
||||
'oxeye_daisy',
|
||||
'cornflower',
|
||||
'wither_rose',
|
||||
'lily_of_the_valley',
|
||||
'brown_mushroom',
|
||||
'red_mushroom',
|
||||
'torch',
|
||||
'wall_torch',
|
||||
'redstone_wire',
|
||||
'wheat',
|
||||
'oak_sign',
|
||||
'spruce_sign',
|
||||
'birch_sign',
|
||||
'acacia_sign',
|
||||
'jungle_sign',
|
||||
'dark_oak_sign',
|
||||
'rail',
|
||||
'oak_wall_sign',
|
||||
'spruce_wall_sign',
|
||||
'birch_wall_sign',
|
||||
'acacia_wall_sign',
|
||||
'jungle_wall_sign',
|
||||
'dark_oak_wall_sign',
|
||||
'lever',
|
||||
'stone_pressure_plate',
|
||||
'oak_pressure_plate',
|
||||
'spruce_pressure_plate',
|
||||
'birch_pressure_plate',
|
||||
'jungle_pressure_plate',
|
||||
'acacia_pressure_plate',
|
||||
'dark_oak_pressure_plate',
|
||||
'redstone_torch',
|
||||
'redstone_wall_torch',
|
||||
'stone_button',
|
||||
'sugar_cane',
|
||||
'repeater',
|
||||
'attached_pumpkin_stem',
|
||||
'attached_melon_stem',
|
||||
'pumpkin_stem',
|
||||
'melon_stem',
|
||||
'nether_wart',
|
||||
'tripwire_hook',
|
||||
'tripwire',
|
||||
'carrots',
|
||||
'potatoes',
|
||||
'oak_button',
|
||||
'spruce_button',
|
||||
'birch_button',
|
||||
'jungle_button',
|
||||
'acacia_button',
|
||||
'dark_oak_button',
|
||||
'light_weighted_pressure_plate',
|
||||
'heavy_weighted_pressure_plate',
|
||||
'comparator',
|
||||
'activator_rail',
|
||||
'white_carpet',
|
||||
'orange_carpet',
|
||||
'magenta_carpet',
|
||||
'light_blue_carpet',
|
||||
'yellow_carpet',
|
||||
'lime_carpet',
|
||||
'pink_carpet',
|
||||
'gray_carpet',
|
||||
'light_gray_carpet',
|
||||
'cyan_carpet',
|
||||
'purple_carpet',
|
||||
'blue_carpet',
|
||||
'brown_carpet',
|
||||
'green_carpet',
|
||||
'red_carpet',
|
||||
'black_carpet',
|
||||
'sunflower',
|
||||
'lilac',
|
||||
'rose_bush',
|
||||
'peony',
|
||||
'tall_grass',
|
||||
'large_fern',
|
||||
'white_banner',
|
||||
'orange_banner',
|
||||
'magenta_banner',
|
||||
'light_blue_banner',
|
||||
'yellow_banner',
|
||||
'lime_banner',
|
||||
'pink_banner',
|
||||
'gray_banner',
|
||||
'light_gray_banner',
|
||||
'cyan_banner',
|
||||
'purple_banner',
|
||||
'blue_banner',
|
||||
'brown_banner',
|
||||
'green_banner',
|
||||
'red_banner',
|
||||
'black_banner',
|
||||
'white_wall_banner',
|
||||
'orange_wall_banner',
|
||||
'magenta_wall_banner',
|
||||
'light_blue_wall_banner',
|
||||
'yellow_wall_banner',
|
||||
'lime_wall_banner',
|
||||
'pink_wall_banner',
|
||||
'gray_wall_banner',
|
||||
'light_gray_wall_banner',
|
||||
'cyan_wall_banner',
|
||||
'purple_wall_banner',
|
||||
'blue_wall_banner',
|
||||
'brown_wall_banner',
|
||||
'green_wall_banner',
|
||||
'red_wall_banner',
|
||||
'black_wall_banner',
|
||||
'beetroots',
|
||||
'bamboo_sapling',
|
||||
'void_air',
|
||||
'cave_air',
|
||||
'lantern',
|
||||
'soul_torch',
|
||||
]
|
||||
|
||||
LOGS = [
|
||||
'oak_log',
|
||||
'spruce_log',
|
||||
'birch_log',
|
||||
'jungle_log',
|
||||
'acacia_log',
|
||||
'dark_oak_log',
|
||||
]
|
||||
|
||||
LEAVES = [
|
||||
'oak_leaves',
|
||||
'spruce_leaves',
|
||||
'birch_leaves',
|
||||
'jungle_leaves',
|
||||
'acacia_leaves',
|
||||
'dark_oak_leaves',
|
||||
]
|
||||
|
||||
SAPLINGS = [
|
||||
'oak_sapling',
|
||||
'spruce_sapling',
|
||||
'birch_sapling',
|
||||
'jungle_sapling',
|
||||
'acacia_sapling',
|
||||
'dark_oak_sapling',
|
||||
]
|
||||
|
||||
BEDS = [
|
||||
'white_bed',
|
||||
'orange_bed',
|
||||
'magenta_bed',
|
||||
'light_blue_bed',
|
||||
'yellow_bed',
|
||||
'lime_bed',
|
||||
'pink_bed',
|
||||
'gray_bed',
|
||||
'light_gray_bed',
|
||||
'cyan_bed',
|
||||
'purple_bed',
|
||||
'blue_bed',
|
||||
'brown_bed',
|
||||
'green_bed',
|
||||
'red_bed',
|
||||
'black_bed',
|
||||
]
|
||||
|
||||
INDEXED = [
|
||||
'chest',
|
||||
'trapped_chest',
|
||||
'emerald_block',
|
||||
'barrel',
|
||||
] + BEDS
|
||||
|
||||
def get_set(ids):
|
||||
def get_set(names):
|
||||
"""Return every state ID belonging to the named blocks; ignore removed names."""
|
||||
result = set()
|
||||
for block_name in ids:
|
||||
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
|
||||
result.add(state['id'])
|
||||
for name in names:
|
||||
name = name.removeprefix('minecraft:')
|
||||
data = MCD_BLOCKS.get(name)
|
||||
if data is not None:
|
||||
result.update(range(data['minStateId'], data['maxStateId'] + 1))
|
||||
return result
|
||||
|
||||
NON_SOLID_IDS = get_set(NON_SOLID)
|
||||
NON_SOLID_IDS.add(SINGLE_SNOW)
|
||||
|
||||
# Collision geometry is safer for navigation than the old hard-coded 1.16 list.
|
||||
NON_SOLID_IDS = {
|
||||
state_id for state_id, boxes in COLLISION_BOXES.items() if not boxes
|
||||
}
|
||||
AVOID = {
|
||||
'lava', 'water', 'fire', 'magma_block', 'sweet_berry_bush',
|
||||
'nether_portal', 'end_portal', 'cactus', 'powder_snow',
|
||||
}
|
||||
AVOID_IDS = get_set(AVOID)
|
||||
AVOID_IDS.update(
|
||||
state_id for state_id, name in BLOCKS.items()
|
||||
if name.endswith(('_fence', '_fence_gate', '_wall'))
|
||||
)
|
||||
WATER_IDS = get_set(['water'])
|
||||
|
||||
LOGS = sorted(name for name in MCD_BLOCKS
|
||||
if name.endswith(('_log', '_wood', '_stem', '_hyphae')))
|
||||
LEAVES = sorted(name for name in MCD_BLOCKS if name.endswith('_leaves'))
|
||||
SAPLINGS = sorted(name for name in MCD_BLOCKS
|
||||
if name.endswith('_sapling') or name.endswith('_propagule'))
|
||||
BEDS = sorted(name for name in MCD_BLOCKS if name.endswith('_bed'))
|
||||
INDEXED = ['chest', 'trapped_chest', 'emerald_block', 'barrel'] + BEDS
|
||||
|
||||
LOG_IDS = get_set(LOGS)
|
||||
LEAF_IDS = get_set(LEAVES)
|
||||
CHEST_IDS = get_set(['chest'])
|
||||
@@ -287,21 +91,28 @@ CARROT_IDS = get_set(['carrots'])
|
||||
BEETROOT_IDS = get_set(['beetroots'])
|
||||
SAPLING_IDS = get_set(SAPLINGS)
|
||||
BED_IDS = get_set(BEDS)
|
||||
|
||||
INDEXED_IDS = set()
|
||||
for block_name in INDEXED:
|
||||
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
|
||||
INDEXED_IDS.add(state['id'])
|
||||
INDEXED_IDS = get_set(INDEXED)
|
||||
|
||||
|
||||
MATURE_WHEAT_ID = max(WHEAT_IDS)
|
||||
MATURE_POTATO_ID = max(POTATO_IDS)
|
||||
MATURE_CARROT_ID = max(CARROT_IDS)
|
||||
MATURE_BEETROOT_ID = max(BEETROOT_IDS)
|
||||
def _mature_state(name, property_name, mature_value):
|
||||
data = MCD_BLOCKS.get(name)
|
||||
if data is None:
|
||||
return None
|
||||
for state_id in range(data['minStateId'], data['maxStateId'] + 1):
|
||||
if PROPS[state_id].get(property_name) == str(mature_value):
|
||||
return state_id
|
||||
return data['maxStateId']
|
||||
|
||||
|
||||
MATURE_WHEAT_ID = _mature_state('wheat', 'age', 7)
|
||||
MATURE_POTATO_ID = _mature_state('potatoes', 'age', 7)
|
||||
MATURE_CARROT_ID = _mature_state('carrots', 'age', 7)
|
||||
MATURE_BEETROOT_ID = _mature_state('beetroots', 'age', 3)
|
||||
|
||||
|
||||
def get(block_state_id):
|
||||
return MCD_BLOCKS[BLOCKS[block_state_id]]
|
||||
|
||||
def get(bid):
|
||||
name = BLOCKS[bid]
|
||||
return MCD_BLOCKS[name]
|
||||
|
||||
def find(name):
|
||||
return MCD_BLOCKS[name]
|
||||
return MCD_BLOCKS[name.removeprefix('minecraft:')]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 grepsedawk <grepsedawk@users.noreply.github.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,7 @@
|
||||
Minecraft 26.3 data files
|
||||
|
||||
Source: RosegoldMC/minecraft-data.cr, data/26.3
|
||||
https://github.com/RosegoldMC/minecraft-data.cr/tree/main/data/26.3
|
||||
|
||||
The JSON files in this directory are redistributed under the MIT License;
|
||||
see LICENSE. They are generated data, not Mojang source code.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
[
|
||||
{"id":0,"name":"aqua_affinity"},
|
||||
{"id":1,"name":"bane_of_arthropods"},
|
||||
{"id":2,"name":"binding_curse"},
|
||||
{"id":3,"name":"blast_protection"},
|
||||
{"id":4,"name":"breach"},
|
||||
{"id":5,"name":"channeling"},
|
||||
{"id":6,"name":"density"},
|
||||
{"id":7,"name":"depth_strider"},
|
||||
{"id":8,"name":"efficiency"},
|
||||
{"id":9,"name":"feather_falling"},
|
||||
{"id":10,"name":"fire_aspect"},
|
||||
{"id":11,"name":"fire_protection"},
|
||||
{"id":12,"name":"flame"},
|
||||
{"id":13,"name":"fortune"},
|
||||
{"id":14,"name":"frost_walker"},
|
||||
{"id":15,"name":"impaling"},
|
||||
{"id":16,"name":"infinity"},
|
||||
{"id":17,"name":"knockback"},
|
||||
{"id":18,"name":"looting"},
|
||||
{"id":19,"name":"loyalty"},
|
||||
{"id":20,"name":"luck_of_the_sea"},
|
||||
{"id":21,"name":"lunge"},
|
||||
{"id":22,"name":"lure"},
|
||||
{"id":23,"name":"mending"},
|
||||
{"id":24,"name":"multishot"},
|
||||
{"id":25,"name":"piercing"},
|
||||
{"id":26,"name":"power"},
|
||||
{"id":27,"name":"projectile_protection"},
|
||||
{"id":28,"name":"protection"},
|
||||
{"id":29,"name":"punch"},
|
||||
{"id":30,"name":"quick_charge"},
|
||||
{"id":31,"name":"respiration"},
|
||||
{"id":32,"name":"riptide"},
|
||||
{"id":33,"name":"sharpness"},
|
||||
{"id":34,"name":"silk_touch"},
|
||||
{"id":35,"name":"smite"},
|
||||
{"id":36,"name":"soul_speed"},
|
||||
{"id":37,"name":"sweeping_edge"},
|
||||
{"id":38,"name":"swift_sneak"},
|
||||
{"id":39,"name":"thorns"},
|
||||
{"id":40,"name":"unbreaking"},
|
||||
{"id":41,"name":"vanishing_curse"},
|
||||
{"id":42,"name":"wind_burst"}
|
||||
]
|
||||
@@ -0,0 +1,163 @@
|
||||
[
|
||||
{"id":0,"name":"acacia_boat","width":1.375,"height":0.5625},
|
||||
{"id":1,"name":"acacia_chest_boat","width":1.375,"height":0.5625},
|
||||
{"id":2,"name":"allay","width":0.35,"height":0.6},
|
||||
{"id":3,"name":"area_effect_cloud","width":6,"height":0.5},
|
||||
{"id":4,"name":"armadillo","width":0.7,"height":0.65},
|
||||
{"id":5,"name":"armor_stand","width":0.5,"height":1.975},
|
||||
{"id":6,"name":"arrow","width":0.5,"height":0.5},
|
||||
{"id":7,"name":"axolotl","width":0.75,"height":0.42},
|
||||
{"id":8,"name":"bamboo_chest_raft","width":1.375,"height":0.5625},
|
||||
{"id":9,"name":"bamboo_raft","width":1.375,"height":0.5625},
|
||||
{"id":10,"name":"bat","width":0.5,"height":0.9},
|
||||
{"id":11,"name":"bee","width":0.7,"height":0.6},
|
||||
{"id":12,"name":"birch_boat","width":1.375,"height":0.5625},
|
||||
{"id":13,"name":"birch_chest_boat","width":1.375,"height":0.5625},
|
||||
{"id":14,"name":"blaze","width":0.6,"height":1.8},
|
||||
{"id":15,"name":"block_display","width":0,"height":0},
|
||||
{"id":16,"name":"bogged","width":0.6,"height":1.99},
|
||||
{"id":17,"name":"breeze","width":0.6,"height":1.77},
|
||||
{"id":18,"name":"breeze_wind_charge","width":0.3125,"height":0.3125},
|
||||
{"id":19,"name":"camel","width":1.7,"height":2.375},
|
||||
{"id":20,"name":"camel_husk","width":1.7,"height":2.375},
|
||||
{"id":21,"name":"cat","width":0.6,"height":0.7},
|
||||
{"id":22,"name":"cave_spider","width":0.7,"height":0.5},
|
||||
{"id":23,"name":"cherry_boat","width":1.375,"height":0.5625},
|
||||
{"id":24,"name":"cherry_chest_boat","width":1.375,"height":0.5625},
|
||||
{"id":25,"name":"chest_minecart","width":0.98,"height":0.7},
|
||||
{"id":26,"name":"chicken","width":0.4,"height":0.7},
|
||||
{"id":27,"name":"cod","width":0.5,"height":0.3},
|
||||
{"id":28,"name":"copper_golem","width":0.49,"height":0.98},
|
||||
{"id":29,"name":"command_block_minecart","width":0.98,"height":0.7},
|
||||
{"id":30,"name":"cow","width":0.9,"height":1.4},
|
||||
{"id":31,"name":"creaking","width":0.9,"height":2.7},
|
||||
{"id":32,"name":"creeper","width":0.6,"height":1.7},
|
||||
{"id":33,"name":"cushion","width":1.0,"height":0.25,"type":"other","category":"Other"},
|
||||
{"id":34,"name":"dark_oak_boat","width":1.375,"height":0.5625},
|
||||
{"id":35,"name":"dark_oak_chest_boat","width":1.375,"height":0.5625},
|
||||
{"id":36,"name":"dolphin","width":0.9,"height":0.6},
|
||||
{"id":37,"name":"donkey","width":1.3964844,"height":1.5},
|
||||
{"id":38,"name":"dragon_fireball","width":1,"height":1},
|
||||
{"id":39,"name":"drowned","width":0.6,"height":1.95},
|
||||
{"id":40,"name":"egg","width":0.25,"height":0.25},
|
||||
{"id":41,"name":"elder_guardian","width":1.9975,"height":1.9975},
|
||||
{"id":42,"name":"enderman","width":0.6,"height":2.9},
|
||||
{"id":43,"name":"endermite","width":0.4,"height":0.3},
|
||||
{"id":44,"name":"ender_dragon","width":16,"height":8},
|
||||
{"id":45,"name":"ender_pearl","width":0.25,"height":0.25},
|
||||
{"id":46,"name":"end_crystal","width":2,"height":2},
|
||||
{"id":47,"name":"evoker","width":0.6,"height":1.95},
|
||||
{"id":48,"name":"evoker_fangs","width":0.5,"height":0.8},
|
||||
{"id":49,"name":"experience_bottle","width":0.25,"height":0.25},
|
||||
{"id":50,"name":"experience_orb","width":0.5,"height":0.5},
|
||||
{"id":51,"name":"eye_of_ender","width":0.25,"height":0.25},
|
||||
{"id":52,"name":"falling_block","width":0.98,"height":0.98},
|
||||
{"id":53,"name":"fireball","width":1,"height":1},
|
||||
{"id":54,"name":"firework_rocket","width":0.25,"height":0.25},
|
||||
{"id":55,"name":"fox","width":0.6,"height":0.7},
|
||||
{"id":56,"name":"frog","width":0.5,"height":0.5},
|
||||
{"id":57,"name":"furnace_minecart","width":0.98,"height":0.7},
|
||||
{"id":58,"name":"ghast","width":4,"height":4},
|
||||
{"id":59,"name":"happy_ghast","width":4,"height":4},
|
||||
{"id":60,"name":"giant","width":3.6,"height":12},
|
||||
{"id":61,"name":"glow_item_frame","width":0.5,"height":0.5},
|
||||
{"id":62,"name":"glow_squid","width":0.8,"height":0.8},
|
||||
{"id":63,"name":"goat","width":0.9,"height":1.3},
|
||||
{"id":64,"name":"guardian","width":0.85,"height":0.85},
|
||||
{"id":65,"name":"hoglin","width":1.3964844,"height":1.4},
|
||||
{"id":66,"name":"hopper_minecart","width":0.98,"height":0.7},
|
||||
{"id":67,"name":"horse","width":1.3964844,"height":1.6},
|
||||
{"id":68,"name":"husk","width":0.6,"height":1.95},
|
||||
{"id":69,"name":"illusioner","width":0.6,"height":1.95},
|
||||
{"id":70,"name":"interaction","width":0,"height":0},
|
||||
{"id":71,"name":"iron_golem","width":1.4,"height":2.7},
|
||||
{"id":72,"name":"item","width":0.25,"height":0.25},
|
||||
{"id":73,"name":"item_display","width":0,"height":0},
|
||||
{"id":74,"name":"item_frame","width":0.5,"height":0.5},
|
||||
{"id":75,"name":"jungle_boat","width":1.375,"height":0.5625},
|
||||
{"id":76,"name":"jungle_chest_boat","width":1.375,"height":0.5625},
|
||||
{"id":77,"name":"leash_knot","width":0.375,"height":0.5},
|
||||
{"id":78,"name":"lightning_bolt","width":0,"height":0},
|
||||
{"id":79,"name":"llama","width":0.9,"height":1.87},
|
||||
{"id":80,"name":"llama_spit","width":0.25,"height":0.25},
|
||||
{"id":81,"name":"magma_cube","width":0.52,"height":0.52},
|
||||
{"id":82,"name":"mangrove_boat","width":1.375,"height":0.5625},
|
||||
{"id":83,"name":"mangrove_chest_boat","width":1.375,"height":0.5625},
|
||||
{"id":84,"name":"mannequin","width":0.6,"height":1.8},
|
||||
{"id":85,"name":"marker","width":0,"height":0},
|
||||
{"id":86,"name":"minecart","width":0.98,"height":0.7},
|
||||
{"id":87,"name":"mooshroom","width":0.9,"height":1.4},
|
||||
{"id":88,"name":"mule","width":1.3964844,"height":1.6},
|
||||
{"id":89,"name":"nautilus","width":0.875,"height":0.95},
|
||||
{"id":90,"name":"oak_boat","width":1.375,"height":0.5625},
|
||||
{"id":91,"name":"oak_chest_boat","width":1.375,"height":0.5625},
|
||||
{"id":92,"name":"ocelot","width":0.6,"height":0.7},
|
||||
{"id":93,"name":"ominous_item_spawner","width":0.25,"height":0.25},
|
||||
{"id":94,"name":"painting","width":0.5,"height":0.5},
|
||||
{"id":95,"name":"pale_oak_boat","width":1.375,"height":0.5625},
|
||||
{"id":96,"name":"pale_oak_chest_boat","width":1.375,"height":0.5625},
|
||||
{"id":97,"name":"panda","width":1.3,"height":1.25},
|
||||
{"id":98,"name":"parched","width":0.6,"height":1.99},
|
||||
{"id":99,"name":"parrot","width":0.5,"height":0.9},
|
||||
{"id":100,"name":"phantom","width":0.9,"height":0.5},
|
||||
{"id":101,"name":"pig","width":0.9,"height":0.9},
|
||||
{"id":102,"name":"piglin","width":0.6,"height":1.95},
|
||||
{"id":103,"name":"piglin_brute","width":0.6,"height":1.95},
|
||||
{"id":104,"name":"pillager","width":0.6,"height":1.95},
|
||||
{"id":105,"name":"polar_bear","width":1.4,"height":1.4},
|
||||
{"id":106,"name":"poplar_boat","width":1.375,"height":0.5625,"type":"other","category":"Vehicles"},
|
||||
{"id":107,"name":"poplar_chest_boat","width":1.375,"height":0.5625,"type":"other","category":"Vehicles"},
|
||||
{"id":108,"name":"splash_potion","width":0.25,"height":0.25},
|
||||
{"id":109,"name":"lingering_potion","width":0.25,"height":0.25},
|
||||
{"id":110,"name":"pufferfish","width":0.7,"height":0.7},
|
||||
{"id":111,"name":"rabbit","width":0.4,"height":0.5},
|
||||
{"id":112,"name":"ravager","width":1.95,"height":2.2},
|
||||
{"id":113,"name":"salmon","width":0.7,"height":0.4},
|
||||
{"id":114,"name":"sheep","width":0.9,"height":1.3},
|
||||
{"id":115,"name":"shulker","width":1,"height":1},
|
||||
{"id":116,"name":"shulker_bullet","width":0.3125,"height":0.3125},
|
||||
{"id":117,"name":"silverfish","width":0.4,"height":0.3},
|
||||
{"id":118,"name":"skeleton","width":0.6,"height":1.99},
|
||||
{"id":119,"name":"skeleton_horse","width":1.3964844,"height":1.6},
|
||||
{"id":120,"name":"slime","width":0.52,"height":0.52},
|
||||
{"id":121,"name":"small_fireball","width":0.3125,"height":0.3125},
|
||||
{"id":122,"name":"sniffer","width":1.9,"height":1.75},
|
||||
{"id":123,"name":"snowball","width":0.25,"height":0.25},
|
||||
{"id":124,"name":"snow_golem","width":0.7,"height":1.9},
|
||||
{"id":125,"name":"spawner_minecart","width":0.98,"height":0.7},
|
||||
{"id":126,"name":"spectral_arrow","width":0.5,"height":0.5},
|
||||
{"id":127,"name":"spider","width":1.4,"height":0.9},
|
||||
{"id":128,"name":"spruce_boat","width":1.375,"height":0.5625},
|
||||
{"id":129,"name":"spruce_chest_boat","width":1.375,"height":0.5625},
|
||||
{"id":130,"name":"squid","width":0.8,"height":0.8},
|
||||
{"id":131,"name":"stray","width":0.6,"height":1.99},
|
||||
{"id":132,"name":"strider","width":0.9,"height":1.7},
|
||||
{"id":133,"name":"sulfur_cube","width":0.49,"height":0.49},
|
||||
{"id":134,"name":"tadpole","width":0.4,"height":0.3},
|
||||
{"id":135,"name":"text_display","width":0,"height":0},
|
||||
{"id":136,"name":"tnt","width":0.98,"height":0.98},
|
||||
{"id":137,"name":"tnt_minecart","width":0.98,"height":0.7},
|
||||
{"id":138,"name":"trader_llama","width":0.9,"height":1.87},
|
||||
{"id":139,"name":"trident","width":0.5,"height":0.5},
|
||||
{"id":140,"name":"tropical_fish","width":0.5,"height":0.4},
|
||||
{"id":141,"name":"turtle","width":1.2,"height":0.4},
|
||||
{"id":142,"name":"vex","width":0.4,"height":0.8},
|
||||
{"id":143,"name":"villager","width":0.6,"height":1.95},
|
||||
{"id":144,"name":"vindicator","width":0.6,"height":1.95},
|
||||
{"id":145,"name":"wandering_trader","width":0.6,"height":1.95},
|
||||
{"id":146,"name":"warden","width":0.9,"height":2.9},
|
||||
{"id":147,"name":"wind_charge","width":0.3125,"height":0.3125},
|
||||
{"id":148,"name":"witch","width":0.6,"height":1.95},
|
||||
{"id":149,"name":"wither","width":0.9,"height":3.5},
|
||||
{"id":150,"name":"wither_skeleton","width":0.7,"height":2.4},
|
||||
{"id":151,"name":"wither_skull","width":0.3125,"height":0.3125},
|
||||
{"id":152,"name":"wolf","width":0.6,"height":0.85},
|
||||
{"id":153,"name":"zoglin","width":1.3964844,"height":1.4},
|
||||
{"id":154,"name":"zombie","width":0.6,"height":1.95},
|
||||
{"id":155,"name":"zombie_horse","width":1.3964844,"height":1.6},
|
||||
{"id":156,"name":"zombie_nautilus","width":0.875,"height":0.95},
|
||||
{"id":157,"name":"zombie_villager","width":0.6,"height":1.95},
|
||||
{"id":158,"name":"zombified_piglin","width":0.6,"height":1.95},
|
||||
{"id":159,"name":"player","width":0.6,"height":1.8},
|
||||
{"id":160,"name":"fishing_bobber","width":0.25,"height":0.25}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"default":{},
|
||||
"sword_instantly_mines":{"1025":2.0,"1030":1.0,"1035":4.0,"1040":12.0,"1045":6.0,"1050":8.0,"1055":9.0},
|
||||
"sword_efficient":{"1025":2.0,"1030":1.0,"1035":4.0,"1040":12.0,"1045":6.0,"1050":8.0,"1055":9.0},
|
||||
"incorrect_for_wooden_tool":{"1026":2.0,"1027":2.0,"1028":2.0,"1029":2.0},
|
||||
"mineable/shovel":{"1026":2.0,"1031":1.0,"1036":4.0,"1041":12.0,"1046":6.0,"1051":8.0,"1056":9.0},
|
||||
"mineable/pickaxe":{"1027":2.0,"1032":1.0,"1037":4.0,"1042":12.0,"1047":6.0,"1052":8.0,"1057":9.0},
|
||||
"mineable/axe":{"1028":2.0,"1033":1.0,"1038":4.0,"1043":12.0,"1048":6.0,"1053":8.0,"1058":9.0},
|
||||
"mineable/hoe":{"1029":2.0,"1034":1.0,"1039":4.0,"1044":12.0,"1049":6.0,"1054":8.0,"1059":9.0},
|
||||
"incorrect_for_copper_tool":{"1031":1.0,"1032":1.0,"1033":1.0,"1034":1.0},
|
||||
"incorrect_for_stone_tool":{"1036":4.0,"1037":4.0,"1038":4.0,"1039":4.0},
|
||||
"incorrect_for_gold_tool":{"1041":12.0,"1042":12.0,"1043":12.0,"1044":12.0},
|
||||
"incorrect_for_iron_tool":{"1046":6.0,"1047":6.0,"1048":6.0,"1049":6.0},
|
||||
"incorrect_for_diamond_tool":{"1051":8.0,"1052":8.0,"1053":8.0,"1054":8.0},
|
||||
"incorrect_for_netherite_tool":{"1056":9.0,"1057":9.0,"1058":9.0,"1059":9.0},
|
||||
"shears_extreme_breaking_speed":{"1255":1.0},
|
||||
"shears_major_breaking_speed":{"1255":1.0},
|
||||
"shears_minor_breaking_speed":{"1255":1.0},
|
||||
"leaves":{"1255":15.0,"1025":1.5,"1030":1.5,"1035":1.5,"1040":1.5,"1045":1.5,"1050":1.5,"1055":1.5},
|
||||
"coweb":{"1255":15.0,"1025":15.0,"1030":15.0,"1035":15.0,"1040":15.0,"1045":15.0,"1050":15.0,"1055":15.0},
|
||||
"vine_or_glow_lichen":{"1255":2.0},
|
||||
"wool":{"1255":5.0},
|
||||
"plant":{"1025":1.5,"1030":1.5,"1035":1.5,"1040":1.5,"1045":1.5,"1050":1.5,"1055":1.5},
|
||||
"gourd":{"1025":1.5,"1030":1.5,"1035":1.5,"1040":1.5,"1045":1.5,"1050":1.5,"1055":1.5},
|
||||
"plant;mineable/axe":{"1025":1.5,"1030":1.5,"1035":1.5,"1040":1.5,"1045":1.5,"1050":1.5,"1055":1.5,"1028":2.0,"1033":1.0,"1038":4.0,"1043":12.0,"1048":6.0,"1053":8.0,"1058":9.0},
|
||||
"gourd;mineable/axe":{"1025":1.5,"1030":1.5,"1035":1.5,"1040":1.5,"1045":1.5,"1050":1.5,"1055":1.5,"1028":2.0,"1033":1.0,"1038":4.0,"1043":12.0,"1048":6.0,"1053":8.0,"1058":9.0},
|
||||
"leaves;mineable/hoe":{"1255":15.0,"1025":1.5,"1030":1.5,"1035":1.5,"1040":1.5,"1045":1.5,"1050":1.5,"1055":1.5,"1029":2.0,"1034":1.0,"1039":4.0,"1044":12.0,"1049":6.0,"1054":8.0,"1059":9.0},
|
||||
"leaves;mineable/axe;mineable/hoe":{
|
||||
"1255":15.0,
|
||||
"1025":1.5,
|
||||
"1030":1.5,
|
||||
"1035":1.5,
|
||||
"1040":1.5,
|
||||
"1045":1.5,
|
||||
"1050":1.5,
|
||||
"1055":1.5,
|
||||
"1028":2.0,
|
||||
"1033":1.0,
|
||||
"1038":4.0,
|
||||
"1043":12.0,
|
||||
"1048":6.0,
|
||||
"1053":8.0,
|
||||
"1058":9.0,
|
||||
"1029":2.0,
|
||||
"1034":1.0,
|
||||
"1039":4.0,
|
||||
"1044":12.0,
|
||||
"1049":6.0,
|
||||
"1054":8.0,
|
||||
"1059":9.0
|
||||
},
|
||||
"vine_or_glow_lichen;plant;mineable/axe":{"1255":2.0,"1025":1.5,"1030":1.5,"1035":1.5,"1040":1.5,"1045":1.5,"1050":1.5,"1055":1.5,"1028":2.0,"1033":1.0,"1038":4.0,"1043":12.0,"1048":6.0,"1053":8.0,"1058":9.0}
|
||||
}
|
||||
+32
-69
@@ -1,78 +1,45 @@
|
||||
import json
|
||||
"""Minecraft 26.3 item registry data and automation selectors."""
|
||||
from mosfet.info import mcdata
|
||||
|
||||
with open('minecraft_data/registries.json') as f:
|
||||
ITEMS = json.load(f)['minecraft:item']['entries']
|
||||
ITEMS = {
|
||||
'minecraft:' + name: dict(item, protocol_id=item['id'])
|
||||
for name, item in mcdata.mcd.items.items()
|
||||
}
|
||||
|
||||
BEDS = [
|
||||
'white_bed',
|
||||
'orange_bed',
|
||||
'magenta_bed',
|
||||
'light_blue_bed',
|
||||
'yellow_bed',
|
||||
'lime_bed',
|
||||
'pink_bed',
|
||||
'gray_bed',
|
||||
'light_gray_bed',
|
||||
'cyan_bed',
|
||||
'purple_bed',
|
||||
'blue_bed',
|
||||
'brown_bed',
|
||||
'green_bed',
|
||||
'red_bed',
|
||||
'black_bed',
|
||||
'white_bed', 'orange_bed', 'magenta_bed', 'light_blue_bed',
|
||||
'yellow_bed', 'lime_bed', 'pink_bed', 'gray_bed', 'light_gray_bed',
|
||||
'cyan_bed', 'purple_bed', 'blue_bed', 'brown_bed', 'green_bed',
|
||||
'red_bed', 'black_bed',
|
||||
]
|
||||
|
||||
SHOVELS = [
|
||||
'wooden_shovel',
|
||||
'stone_shovel',
|
||||
'golden_shovel',
|
||||
'iron_shovel',
|
||||
'diamond_shovel',
|
||||
'netherite_shovel',
|
||||
'wooden_shovel', 'stone_shovel', 'golden_shovel', 'iron_shovel',
|
||||
'diamond_shovel', 'netherite_shovel',
|
||||
]
|
||||
|
||||
AXES = [
|
||||
'wooden_axe',
|
||||
'stone_axe',
|
||||
'golden_axe',
|
||||
'iron_axe',
|
||||
'diamond_axe',
|
||||
'netherite_axe',
|
||||
'wooden_axe', 'stone_axe', 'golden_axe', 'iron_axe',
|
||||
'diamond_axe', 'netherite_axe',
|
||||
]
|
||||
|
||||
SAPLINGS = [
|
||||
'oak_sapling',
|
||||
'spruce_sapling',
|
||||
'birch_sapling',
|
||||
'jungle_sapling',
|
||||
'acacia_sapling',
|
||||
'dark_oak_sapling',
|
||||
name for name in mcdata.mcd.blocks
|
||||
if name.endswith('_sapling') or name.endswith('_propagule')
|
||||
]
|
||||
|
||||
FOOD = [
|
||||
'cooked_porkchop',
|
||||
'cooked_beef',
|
||||
'bread',
|
||||
'cooked_chicken',
|
||||
'cooked_cod',
|
||||
'cooked_salmon',
|
||||
'cooked_porkchop', 'cooked_beef', 'bread', 'cooked_chicken',
|
||||
'cooked_cod', 'cooked_salmon',
|
||||
]
|
||||
|
||||
LOGS = [
|
||||
'oak_log',
|
||||
'spruce_log',
|
||||
'birch_log',
|
||||
'jungle_log',
|
||||
'acacia_log',
|
||||
'dark_oak_log',
|
||||
name for name in mcdata.mcd.blocks
|
||||
if name.endswith(('_log', '_wood', '_stem', '_hyphae'))
|
||||
]
|
||||
|
||||
|
||||
def get_set(ids):
|
||||
result = set()
|
||||
for item_name in ids:
|
||||
result.add(ITEMS['minecraft:'+item_name]['protocol_id'])
|
||||
return result
|
||||
def get_set(names):
|
||||
return {
|
||||
ITEMS['minecraft:' + name]['protocol_id']
|
||||
for name in names if 'minecraft:' + name in ITEMS
|
||||
}
|
||||
|
||||
|
||||
BED_IDS = get_set(BEDS)
|
||||
SHOVEL_IDS = get_set(SHOVELS)
|
||||
@@ -80,23 +47,22 @@ AXE_IDS = get_set(AXES)
|
||||
FOOD_IDS = get_set(FOOD)
|
||||
SAPLING_IDS = get_set(SAPLINGS)
|
||||
LOG_IDS = get_set(LOGS)
|
||||
|
||||
ITEM_NAMES = {}
|
||||
for item_name, item in ITEMS.items():
|
||||
ITEM_NAMES[ITEMS[item_name]['protocol_id']] = item_name.replace('minecraft:', '')
|
||||
ITEM_NAMES = {item['protocol_id']: name.removeprefix('minecraft:')
|
||||
for name, item in ITEMS.items()}
|
||||
|
||||
|
||||
def get_id(name):
|
||||
return ITEMS['minecraft:' + name]['protocol_id']
|
||||
|
||||
|
||||
def get_name(idx):
|
||||
return ITEM_NAMES[idx]
|
||||
|
||||
|
||||
CHEST_ID = get_id('chest')
|
||||
GAPPLE_ID = get_id('enchanted_golden_apple')
|
||||
SAND_ID = get_id('sand')
|
||||
NETHERWART_ID = get_id('nether_wart')
|
||||
|
||||
CARROT_ID = get_id('carrot')
|
||||
POTATO_ID = get_id('potato')
|
||||
WHEAT_ID = get_id('wheat')
|
||||
@@ -104,25 +70,22 @@ WHEAT_SEEDS_ID = get_id('wheat_seeds')
|
||||
BEETROOT_SEEDS_ID = get_id('beetroot_seeds')
|
||||
PUMPKIN_ID = get_id('pumpkin')
|
||||
BEETROOT_ID = get_id('beetroot')
|
||||
|
||||
EMERALD_ID = get_id('emerald')
|
||||
BERRIES_ID = get_id('sweet_berries')
|
||||
IRON_INGOT_ID = get_id('iron_ingot')
|
||||
|
||||
|
||||
INIT_NEEDED_ITEMS = BED_IDS | FOOD_IDS
|
||||
INIT_NEEDED_ITEMS.add(CHEST_ID)
|
||||
|
||||
NEEDED_ITEMS = INIT_NEEDED_ITEMS
|
||||
|
||||
INIT_WANTED_ITEMS = set()
|
||||
WANTED_ITEMS = INIT_WANTED_ITEMS
|
||||
|
||||
|
||||
def set_needed(items):
|
||||
global NEEDED_ITEMS
|
||||
NEEDED_ITEMS = INIT_NEEDED_ITEMS | items
|
||||
|
||||
|
||||
def set_wanted(items):
|
||||
global WANTED_ITEMS
|
||||
WANTED_ITEMS = INIT_WANTED_ITEMS | items
|
||||
|
||||
|
||||
+30
-26
@@ -1,32 +1,36 @@
|
||||
"""Minecraft 26.3 client data used by the automation layer.
|
||||
|
||||
The generated data is vendored under ``mosfet/info/data/26.3`` from
|
||||
RosegoldMC/minecraft-data.cr (MIT); see that directory's LICENSE.
|
||||
"""
|
||||
import json
|
||||
from munch import Munch
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import minecraft_data
|
||||
DATA_VERSION = '26.3'
|
||||
DATA_DIR = Path(__file__).resolve().parent / 'data' / DATA_VERSION
|
||||
|
||||
mcd = minecraft_data('1.16.4')
|
||||
|
||||
with open('minecraft_data/registries.json') as f:
|
||||
DATA = json.load(f)
|
||||
def _load(name):
|
||||
with (DATA_DIR / name).open(encoding='utf-8') as source:
|
||||
return json.load(source)
|
||||
|
||||
SINGLE_CHEST = 2
|
||||
DOUBLE_CHEST = 5
|
||||
VILLAGER_TRADE = 18
|
||||
|
||||
WINDOWS = {
|
||||
SINGLE_CHEST: Munch(
|
||||
container=list(range(0, 27)),
|
||||
inventory=list(range(27, 63)),
|
||||
slot_diff=18,
|
||||
),
|
||||
DOUBLE_CHEST: Munch(
|
||||
container=list(range(0, 54)),
|
||||
inventory=list(range(54, 90)),
|
||||
slot_diff=45,
|
||||
),
|
||||
VILLAGER_TRADE: Munch(
|
||||
input1=0,
|
||||
input2=1,
|
||||
output=2,
|
||||
inventory=list(range(3, 38)),
|
||||
),
|
||||
}
|
||||
block_list = _load('blocks.json')
|
||||
item_list = _load('items.json')
|
||||
entity_list = _load('entities.json')
|
||||
materials = _load('materials.json')
|
||||
collision_shapes = _load('blockCollisionShapes.json')
|
||||
|
||||
blocks = {block['name']: block for block in block_list}
|
||||
items = {item['name']: item for item in item_list}
|
||||
entities = {entity['name']: entity for entity in entity_list}
|
||||
|
||||
mcd = SimpleNamespace(
|
||||
version=DATA_VERSION,
|
||||
blocks=blocks,
|
||||
items=items,
|
||||
entities=entities,
|
||||
materials=materials,
|
||||
collision_shapes=collision_shapes,
|
||||
)
|
||||
|
||||
+22
-49
@@ -1,10 +1,13 @@
|
||||
from mosfet.info import items
|
||||
import json
|
||||
"""Minecraft 26.3 entity registry data and legacy mob selectors."""
|
||||
from mosfet.info import items, mcdata
|
||||
|
||||
with open('minecraft_data/registries.json') as f:
|
||||
MOBS = json.load(f)['minecraft:entity_type']['entries']
|
||||
MOBS = {
|
||||
'minecraft:' + name: dict(entity, protocol_id=entity['id'])
|
||||
for name, entity in mcdata.mcd.entities.items()
|
||||
}
|
||||
VILLAGER = MOBS['minecraft:villager']['protocol_id']
|
||||
|
||||
VILLAGER = 93
|
||||
# Villager profession enum values remain the protocol's 0-based profession IDs.
|
||||
ARMORER = 1
|
||||
BUTCHER = 2
|
||||
CARTOGRAPHER = 3
|
||||
@@ -23,53 +26,23 @@ WEAPONSMITH = 14
|
||||
TRADES = {
|
||||
ARMORER: [items.IRON_INGOT_ID],
|
||||
BUTCHER: [items.BERRIES_ID],
|
||||
FARMER: [items.PUMPKIN_ID, items.WHEAT_ID, items.POTATO_ID, items.CARROT_ID, items.BEETROOT_ID],
|
||||
FARMER: [items.PUMPKIN_ID, items.WHEAT_ID, items.POTATO_ID,
|
||||
items.CARROT_ID, items.BEETROOT_ID],
|
||||
TOOLSMITH: [items.IRON_INGOT_ID],
|
||||
WEAPONSMITH: [items.IRON_INGOT_ID],
|
||||
}
|
||||
|
||||
EVIL = [
|
||||
'blaze',
|
||||
'cave_spider',
|
||||
'creeper',
|
||||
'drowned',
|
||||
'elder_guardian',
|
||||
'ender_dragon',
|
||||
'enderman',
|
||||
'endermite',
|
||||
'evoker',
|
||||
'ghast',
|
||||
'giant',
|
||||
'guardian',
|
||||
'hoglin',
|
||||
'husk',
|
||||
'illusioner',
|
||||
'magma_cube',
|
||||
'phantom',
|
||||
'piglin',
|
||||
'piglin_brute',
|
||||
'pillager',
|
||||
'ravager',
|
||||
'shulker',
|
||||
'silverfish',
|
||||
'skeleton',
|
||||
'skeleton_horse',
|
||||
'slime',
|
||||
'spider',
|
||||
'stray',
|
||||
'vex',
|
||||
'vindicator',
|
||||
'witch',
|
||||
'wither',
|
||||
'zoglin',
|
||||
'zombie',
|
||||
'zombie_villager',
|
||||
'blaze', 'cave_spider', 'creeper', 'drowned', 'elder_guardian',
|
||||
'ender_dragon', 'enderman', 'endermite', 'evoker', 'ghast', 'giant',
|
||||
'guardian', 'hoglin', 'husk', 'illusioner', 'magma_cube', 'phantom',
|
||||
'piglin', 'piglin_brute', 'pillager', 'ravager', 'shulker', 'silverfish',
|
||||
'skeleton', 'skeleton_horse', 'slime', 'spider', 'stray', 'vex',
|
||||
'vindicator', 'witch', 'wither', 'zoglin', 'zombie', 'zombie_villager',
|
||||
]
|
||||
|
||||
EVIL_IDS = set()
|
||||
for mob_name in EVIL:
|
||||
EVIL_IDS.add(MOBS['minecraft:'+mob_name]['protocol_id'])
|
||||
|
||||
MOB_NAMES = {}
|
||||
for mob_name, mob in MOBS.items():
|
||||
MOB_NAMES[MOBS[mob_name]['protocol_id']] = mob_name.replace('minecraft:', '')
|
||||
EVIL_IDS = {
|
||||
MOBS['minecraft:' + name]['protocol_id']
|
||||
for name in EVIL if 'minecraft:' + name in MOBS
|
||||
}
|
||||
MOB_NAMES = {entity['protocol_id']: name.removeprefix('minecraft:')
|
||||
for name, entity in MOBS.items()}
|
||||
|
||||
+4
-5
@@ -121,7 +121,7 @@ class Pathfinder(AStar):
|
||||
def __init__(self, g):
|
||||
self.g = g
|
||||
self.chunks = g.chunks
|
||||
self.start_time = time.time()
|
||||
self.start_time = time.monotonic()
|
||||
|
||||
@functools.lru_cache(maxsize=BLOCK_CACHE_SIZE)
|
||||
def bair(self, p):
|
||||
@@ -267,6 +267,9 @@ class Pathfinder(AStar):
|
||||
return True
|
||||
|
||||
def neighbors(self, node):
|
||||
if time.monotonic() - self.start_time > 2.0:
|
||||
raise AStarTimeout
|
||||
|
||||
results = []
|
||||
|
||||
if self.g.crawling:
|
||||
@@ -296,10 +299,6 @@ class Pathfinder(AStar):
|
||||
if self.check_parkour(node, offset):
|
||||
results.append(utils.padd(node, offset))
|
||||
|
||||
if not results:
|
||||
if time.time() - self.start_time > 2.0:
|
||||
raise(AStarTimeout)
|
||||
|
||||
return results
|
||||
|
||||
def distance_between(self, n1, n2):
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
"""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]
|
||||
+11
-30
@@ -1,42 +1,23 @@
|
||||
import os
|
||||
from math import floor
|
||||
import json
|
||||
import time
|
||||
from math import floor
|
||||
|
||||
from minecraft.networking.packets import clientbound, serverbound
|
||||
from mosfet.protocol import packets
|
||||
from mosfet.info import blocks, mcdata, mobs
|
||||
|
||||
from mosfet import utils
|
||||
from mosfet.info import blocks
|
||||
|
||||
class DataManager:
|
||||
def __init__(self, directory):
|
||||
self.blocks = {}
|
||||
self.blocks_states = {}
|
||||
self.blocks_properties = {}
|
||||
"""Compatibility facade over the checked-in Minecraft 26.3 data."""
|
||||
def __init__(self, directory=None):
|
||||
self.blocks = mcdata.mcd.blocks
|
||||
self.blocks_states = dict(blocks.BLOCKS)
|
||||
self.blocks_properties = dict(blocks.PROPS)
|
||||
self.registries = {}
|
||||
self.biomes = {}
|
||||
self.entity_type = {}
|
||||
|
||||
if not os.path.isdir(directory):
|
||||
raise FileNotFoundError("%s is not a valid directory")
|
||||
|
||||
if not os.path.isfile("%s/registries.json"%(directory)):
|
||||
raise FileNotFoundError("%s is not a valid minecraft data directory")
|
||||
|
||||
with open("%s/blocks.json"%(directory)) as f:
|
||||
blocks = json.loads(f.read())
|
||||
for x in blocks:
|
||||
for s in blocks[x]['states']:
|
||||
self.blocks_states[s['id']] = x
|
||||
self.blocks_properties[s['id']] = s.get('properties', {})
|
||||
|
||||
with open("%s/registries.json"%(directory)) as f:
|
||||
registries = json.loads(f.read())
|
||||
#for x in registries["minecraft:biome"]["entries"]:
|
||||
# self.biomes[registries["minecraft:biome"]["entries"][x]["protocol_id"]] = x
|
||||
for x in registries["minecraft:entity_type"]["entries"]:
|
||||
self.entity_type[registries["minecraft:entity_type"]["entries"][x]["protocol_id"]] = x
|
||||
self.entity_type = {
|
||||
data['protocol_id']: name
|
||||
for name, data in mobs.MOBS.items()
|
||||
}
|
||||
|
||||
|
||||
class ChunksManager:
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Chat adapter for the Minecraft 26.3 play protocol."""
|
||||
import time
|
||||
|
||||
from minecraft.networking.modern import (
|
||||
ChatCommand, ClientboundPlayerChatPacket, ServerboundChatPacket, SystemChat,
|
||||
)
|
||||
|
||||
|
||||
class ModernChatManager:
|
||||
def __init__(self, global_state):
|
||||
self.g = global_state
|
||||
self.handler = None
|
||||
self.g.connection.register_packet_listener(
|
||||
self.handle_player_chat, ClientboundPlayerChatPacket)
|
||||
|
||||
def set_handler(self, callback):
|
||||
self.handler = callback
|
||||
|
||||
def handle_player_chat(self, packet):
|
||||
sender = str(packet.sender)
|
||||
text = '{}: {}'.format(sender, packet.message)
|
||||
print('[chat] {}: {}'.format(sender, packet.message), flush=True)
|
||||
if self.handler:
|
||||
self.handler(('CHAT', sender, text))
|
||||
|
||||
def handle_system_chat(self, packet, message):
|
||||
if self.handler:
|
||||
self.handler(('SYSTEM', None, message))
|
||||
|
||||
def send(self, message):
|
||||
if not message:
|
||||
return
|
||||
if message.startswith('/'):
|
||||
self.g.connection.write_packet(
|
||||
ChatCommand(command=message[1:]), force=True)
|
||||
else:
|
||||
packet = ServerboundChatPacket(
|
||||
message=message, timestamp=int(time.time() * 1000))
|
||||
self.g.connection.write_packet(packet, force=True)
|
||||
@@ -0,0 +1,220 @@
|
||||
"""Small 26.3 world cache used while the legacy automation is being ported.
|
||||
|
||||
It retains numeric block-state IDs from chunk palettes and block updates. It
|
||||
intentionally does not assign 1.16 block meanings to modern state IDs.
|
||||
"""
|
||||
from math import floor
|
||||
|
||||
|
||||
class PaletteSection:
|
||||
def __init__(self, bits, palette, words, size):
|
||||
self.bits = bits
|
||||
self.palette = palette
|
||||
self.words = words
|
||||
self.size = size
|
||||
self.overrides = {}
|
||||
self.mask = (1 << bits) - 1 if bits else 0
|
||||
self.per_word = 64 // bits if bits else 0
|
||||
|
||||
def get(self, index):
|
||||
if index in self.overrides:
|
||||
return self.overrides[index]
|
||||
if self.bits == 0:
|
||||
return self.palette[0]
|
||||
word = self.words[index // self.per_word]
|
||||
palette_index = (word >> ((index % self.per_word) * self.bits)) & self.mask
|
||||
return self.palette[palette_index] if self.palette is not None else palette_index
|
||||
|
||||
def set(self, index, block_state_id):
|
||||
self.overrides[index] = block_state_id
|
||||
|
||||
|
||||
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 loaded-chunk/block-state cache with the old manager's read API."""
|
||||
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):
|
||||
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)
|
||||
|
||||
|
||||
def read_palette_container(stream, *, size, local_bits_max):
|
||||
from minecraft.networking.types import Byte, UnsignedLong, VarInt
|
||||
|
||||
bits = Byte.read(stream)
|
||||
if bits == 0:
|
||||
palette = [VarInt.read(stream)]
|
||||
elif bits <= local_bits_max:
|
||||
palette_size = VarInt.read(stream)
|
||||
palette = [VarInt.read(stream) for _ in range(palette_size)]
|
||||
else:
|
||||
palette = None
|
||||
|
||||
word_count = ((size + (64 // bits) - 1) // (64 // bits)) if bits else 0
|
||||
words = [UnsignedLong.read(stream) for _ in range(word_count)]
|
||||
return PaletteSection(bits, palette, words, size)
|
||||
|
||||
|
||||
def decode_chunk_sections(data):
|
||||
from io import BytesIO
|
||||
from minecraft.networking.types import Short
|
||||
|
||||
stream = BytesIO(data)
|
||||
sections = []
|
||||
while stream.tell() < len(data):
|
||||
start = stream.tell()
|
||||
if len(data) - start < 4:
|
||||
raise ValueError('truncated chunk section header at byte {}'.format(start))
|
||||
non_empty_blocks = Short.read(stream)
|
||||
fluid_count = Short.read(stream)
|
||||
blocks = read_palette_container(stream, size=4096, local_bits_max=8)
|
||||
biomes = read_palette_container(stream, size=64, local_bits_max=3)
|
||||
sections.append({
|
||||
'non_empty_blocks': non_empty_blocks,
|
||||
'fluid_count': fluid_count,
|
||||
'blocks': blocks,
|
||||
'biomes': biomes,
|
||||
})
|
||||
if stream.tell() <= start:
|
||||
raise ValueError('chunk section decoder made no progress')
|
||||
return sections
|
||||
Reference in New Issue
Block a user