Consolidate bot runtime on Minecraft 26.3

This commit is contained in:
Tanner
2026-11-04 03:05:23 +00:00
parent b6f7ce9948
commit f7f58f5d07
15 changed files with 706 additions and 537 deletions
+133 -27
View File
@@ -6,6 +6,8 @@ import os
import sys
import time
import importlib
import threading
import traceback
from math import floor, ceil
from copy import copy
@@ -68,26 +70,51 @@ def tick(global_state):
required_distance = getattr(g, 'physics_render_distance',
g.info.render_distance)
if not g.chunks.check_loaded(required_distance):
if g.get('movement_status') != 'waiting_for_chunks':
print('[movement] status=waiting_for_chunks near {}'.format(
utils.pint(p)), flush=True)
g.movement_status = 'waiting_for_chunks'
if not g.chunks.loading:
print('Loading chunks', end='', flush=True)
g.chunks.loading = time.time()
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)
now = time.time()
if now - g.get('chunk_wait_report_at', 0) >= 5:
center = g.chunks.center
if center is None:
missing = 'cache center not received'
else:
cx, cz = center
missing = [
(x, z)
for x in range(cx - required_distance,
cx + required_distance + 1)
for z in range(cz - required_distance,
cz + required_distance + 1)
if (x, z) not in g.chunks.chunks
]
print('\n[movement] chunk wait: pos={}, center={}, '
'radius={}, loaded={}, missing={}'.format(
utils.pint(p), center, g.chunks.radius,
len(g.chunks.chunks), missing), flush=True)
g.chunk_wait_report_at = now
from minecraft.networking.modern import (
ClientTickEnd, PlayerPositionRotation,
)
packet = PlayerPositionRotation(
x=p.x, 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)
g.connection.write_packet(ClientTickEnd(), force=True)
return
else:
if g.chunks.loading:
print()
print('Chunks loaded in', round(time.time() - g.chunks.loading, 2), 's')
g.chunks.loading = False
g.chunk_wait_report_at = 0
status = 'following_path' if g.path else 'running_job'
if g.get('movement_status') != status:
print('[movement] status={}'.format(status), flush=True)
g.movement_status = status
########## object physics ##########
# note: it's possible the chunk data is out of date when this runs
@@ -208,23 +235,15 @@ def tick(global_state):
target_pitch_d = 0 - g.pitch
g.pitch += utils.cap(target_pitch_d, 10)
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)
from minecraft.networking.modern import (
ClientTickEnd, PlayerPositionRotation,
)
packet = PlayerPositionRotation(
x=p.x, y=p.y, z=p.z, pitch=g.pitch, yaw=g.yaw,
on_ground=(not in_air))
# Keep the client-tick boundary after its movement packet on the wire.
g.connection.write_packet(packet, force=True)
g.connection.write_packet(ClientTickEnd(), force=True)
if g.job:
g.job.tick()
@@ -352,3 +371,90 @@ def bot(global_state):
g.connection.early_outgoing_packet_listeners = []
print('Bot module loaded.')
def start_automation(global_state):
"""Start migrated path-following and crop automation only."""
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
# Keep the port constrained to crop harvesting; other legacy jobs and
# general inventory/window mutations are not exposed through this adapter.
from mosfet.game import Game
from mosfet.job import JobStates
g.game = Game(g)
g.job = JobStates(g)
def run():
from mosfet import bot, utils
loaded_since = None
initial_world_ready = False
def set_status(status):
if g.movement_status != status:
print('[movement] status={}'.format(status), flush=True)
g.movement_status = status
while (g.get('running', True) and g.get('connection') and
g.connection.connected):
started = time.monotonic()
now = time.monotonic()
if not initial_world_ready:
if g.chunks.check_loaded(g.physics_render_distance):
if loaded_since is None:
loaded_since = now
elif now - loaded_since >= 0.5:
initial_world_ready = True
else:
loaded_since = None
# Wait once for the initial position/chunk batch. After that,
# bot.tick() keeps requesting chunks at cache boundaries instead
# of gating itself forever on a momentary missing column.
movement_ready = initial_world_ready
job_active = (g.job is not None and g.job.state != g.job.idle)
has_work = bool(g.path) or job_active or bool(g.breaking)
if g.get('movement_paused'):
set_status('paused')
elif has_work and not movement_ready:
set_status('waiting_for_chunks')
elif has_work and movement_ready:
try:
bot.tick(g)
except Exception:
traceback.print_exc()
g.movement_error = traceback.format_exc()
g.path = []
g.movement_status = 'error'
print('[movement] stopped after navigation error',
flush=True)
return
elif not has_work and not g.get('movement_paused'):
set_status('idle')
delay = max(0.0, utils.TICK - (time.monotonic() - started))
time.sleep(delay)
worker = threading.Thread(target=run, name='mosfet-movement',
daemon=True)
g.movement_thread = worker
worker.start()
return worker