Implement item cachine for 26.3
This commit is contained in:
@@ -48,9 +48,11 @@ the next manual start, rather than triggering an automatic reconnect.
|
|||||||
|
|
||||||
The 26.3 client now handles AuthMe, chat commands, chunk/world-state caching,
|
The 26.3 client now handles AuthMe, chat commands, chunk/world-state caching,
|
||||||
modern block/item/entity data, entity tracking, inventory/component sync,
|
modern block/item/entity data, entity tracking, inventory/component sync,
|
||||||
path-following movement (`1goto x y z`), inventory draining (`1drain`), and the
|
path-following movement (`1goto x y z`), inventory draining (`1drain`),
|
||||||
crop harvest/replant loop (`1farm crops`). Other inventory/window actions and
|
normal/trapped-chest caching (`1cache`, also run by crop farming at the
|
||||||
the farming/trading jobs remain disabled until their 26.3 packets are migrated.
|
inventory threshold), and the crop harvest/replant loop (`1farm crops`). Other
|
||||||
|
inventory/window actions and 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
|
The default server is `127.0.0.1`; set `SERVER` explicitly only when you
|
||||||
intentionally want another server.
|
intentionally want another server.
|
||||||
|
|
||||||
@@ -72,10 +74,10 @@ would be ran like `1farm wood` or `1pos`.
|
|||||||
The exception are the below public commands, they can optionally be prefixed with `!`
|
The exception are the below public commands, they can optionally be prefixed with `!`
|
||||||
and all bots will run the command.
|
and all bots will run the command.
|
||||||
|
|
||||||
The current 26.3 command path handles public chat. Private-message routing is
|
The current 26.3 command path handles public chat and direct messages, replying
|
||||||
not migrated yet. Verified commands are `!ping`, `!pos`, `!loaded`, `!info x y
|
in the same channel. Verified commands are `!ping`, `!pos`, `!loaded`,
|
||||||
z`, `!inv` (basic slot/count data), `1drain`, addressed path movement such as
|
`!info x y z`, `!inv` (basic slot/count data), `1cache`, `1drain`, addressed
|
||||||
`1goto x y z`, and `1farm crops`. The command catalog below is retained from
|
path movement such as `1goto x y z`, and `1farm crops`. The command catalog below is retained from
|
||||||
the legacy bot; other inventory actions, arbitrary block interaction, and the
|
the legacy bot; other inventory actions, arbitrary block interaction, and the
|
||||||
other farming/trading commands are not yet available on 26.3. Common item
|
other farming/trading commands are not yet available on 26.3. Common item
|
||||||
components (names, damage, enchantments, book
|
components (names, damage, enchantments, book
|
||||||
|
|||||||
@@ -395,6 +395,8 @@ def start_automation(global_state, initialize=True):
|
|||||||
g.players = g.get('players') or {}
|
g.players = g.get('players') or {}
|
||||||
g.breaking = None
|
g.breaking = None
|
||||||
g.break_time = 0.0
|
g.break_time = 0.0
|
||||||
|
g.item_lock = False
|
||||||
|
g.minimum_cache_slots = g.get('minimum_cache_slots', 27)
|
||||||
g.draining = False
|
g.draining = False
|
||||||
g.drain_slots = []
|
g.drain_slots = []
|
||||||
g.drain_waiting = None
|
g.drain_waiting = None
|
||||||
@@ -404,6 +406,7 @@ def start_automation(global_state, initialize=True):
|
|||||||
g.correction_count = g.get('correction_count', 0)
|
g.correction_count = g.get('correction_count', 0)
|
||||||
g.yaw = float(g.get('yaw', 0.0) or 0.0)
|
g.yaw = float(g.get('yaw', 0.0) or 0.0)
|
||||||
g.pitch = float(g.get('pitch', 0.0) or 0.0)
|
g.pitch = float(g.get('pitch', 0.0) or 0.0)
|
||||||
|
g.holding_slot = g.get('holding_slot', 0)
|
||||||
g.chunks.loading = False
|
g.chunks.loading = False
|
||||||
# Readiness below is based on nearby chunks, not unrelated view holes.
|
# Readiness below is based on nearby chunks, not unrelated view holes.
|
||||||
g.movement_paused = False
|
g.movement_paused = False
|
||||||
@@ -423,6 +426,9 @@ def start_automation(global_state, initialize=True):
|
|||||||
g.setdefault('drain_dropped', 0)
|
g.setdefault('drain_dropped', 0)
|
||||||
g.setdefault('drain_total', 0)
|
g.setdefault('drain_total', 0)
|
||||||
g.setdefault('drain_error', None)
|
g.setdefault('drain_error', None)
|
||||||
|
g.setdefault('item_lock', False)
|
||||||
|
g.setdefault('minimum_cache_slots', 27)
|
||||||
|
g.setdefault('holding_slot', 0)
|
||||||
|
|
||||||
old_stop = g.get('movement_stop_event')
|
old_stop = g.get('movement_stop_event')
|
||||||
old_worker = g.get('movement_thread')
|
old_worker = g.get('movement_thread')
|
||||||
|
|||||||
+1
-1
@@ -66,7 +66,7 @@ class Commands:
|
|||||||
command = 'nosquarebrackets'
|
command = 'nosquarebrackets'
|
||||||
|
|
||||||
unsupported = {
|
unsupported = {
|
||||||
'gather', 'loiter', 'trade', 'gapple', 'cache', 'fill',
|
'gather', 'loiter', 'trade', 'gapple', 'fill',
|
||||||
'drop', 'dump', 'break', 'open', 'click', 'use',
|
'drop', 'dump', 'break', 'open', 'click', 'use',
|
||||||
'interact', 'respawn',
|
'interact', 'respawn',
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-1
@@ -48,6 +48,27 @@ class ContainerClickDropPacket(Packet):
|
|||||||
VarInt.send(0, packet_buffer) # empty carried item stack
|
VarInt.send(0, packet_buffer) # empty carried item stack
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerClickQuickMovePacket(Packet):
|
||||||
|
"""Shift-click an inventory slot into the open container (protocol 777)."""
|
||||||
|
id = 0x12
|
||||||
|
packet_name = 'container click quick move'
|
||||||
|
|
||||||
|
def write_fields(self, packet_buffer):
|
||||||
|
VarInt.send(self.container_id, packet_buffer)
|
||||||
|
VarInt.send(self.state_id, packet_buffer)
|
||||||
|
Short.send(self.slot, packet_buffer)
|
||||||
|
Byte.send(0, packet_buffer) # mouse button
|
||||||
|
VarInt.send(1, packet_buffer) # ContainerInput.QUICK_MOVE
|
||||||
|
VarInt.send(0, packet_buffer) # no changed-slot predictions
|
||||||
|
VarInt.send(0, packet_buffer) # empty carried item stack
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerClosePacket(Packet):
|
||||||
|
id = 0x13
|
||||||
|
packet_name = 'container close'
|
||||||
|
definition = [{'container_id': VarInt}]
|
||||||
|
|
||||||
|
|
||||||
class LegacyGame:
|
class LegacyGame:
|
||||||
def __init__(self, global_state):
|
def __init__(self, global_state):
|
||||||
self.g = global_state
|
self.g = global_state
|
||||||
@@ -795,6 +816,33 @@ class Game(LegacyGame):
|
|||||||
sequence=self._next_sequence())
|
sequence=self._next_sequence())
|
||||||
self._send_action(packet)
|
self._send_action(packet)
|
||||||
|
|
||||||
|
def count_window_slots(self):
|
||||||
|
window = self.g.get('window')
|
||||||
|
if not window:
|
||||||
|
return 0
|
||||||
|
return sum(
|
||||||
|
item.present
|
||||||
|
for slot, item in window.contents.items()
|
||||||
|
if slot < window.container_slots)
|
||||||
|
|
||||||
|
def click_window(self, slot, button=0, mode=1, item=None):
|
||||||
|
window = self.g.get('window')
|
||||||
|
if not window:
|
||||||
|
return False
|
||||||
|
packet = ContainerClickQuickMovePacket(
|
||||||
|
container_id=window.window_id,
|
||||||
|
state_id=self.g.inventory_state_ids.get(
|
||||||
|
window.window_id, window.state_id),
|
||||||
|
slot=slot,
|
||||||
|
)
|
||||||
|
self.g.connection.write_packet(packet, force=True)
|
||||||
|
self.g.connection.write_packet(ClientTickEnd(), force=True)
|
||||||
|
return True
|
||||||
|
|
||||||
def close_window(self):
|
def close_window(self):
|
||||||
# Container open/close packet migration is not part of the crop path.
|
window = self.g.get('window')
|
||||||
|
if not window:
|
||||||
return None
|
return None
|
||||||
|
self.g.connection.write_packet(ContainerClosePacket(
|
||||||
|
container_id=window.window_id), force=True)
|
||||||
|
self.g.window = None
|
||||||
|
|||||||
+12
-1
@@ -269,16 +269,27 @@ class JobStates(LegacyJobStates):
|
|||||||
def __init__(self, global_state):
|
def __init__(self, global_state):
|
||||||
self.g = global_state
|
self.g = global_state
|
||||||
self.gather_crop_states = gather_crop.GatherCropStates(global_state)
|
self.gather_crop_states = gather_crop.GatherCropStates(global_state)
|
||||||
|
self.cache_items_states = cache_items.CacheItemsStates(global_state)
|
||||||
self.state = self.idle
|
self.state = self.idle
|
||||||
|
|
||||||
def farm_crop(self):
|
def farm_crop(self):
|
||||||
return [self.gather_crop_states]
|
self.cache_items_states.silent = True
|
||||||
|
items.set_wanted({
|
||||||
|
items.CARROT_ID,
|
||||||
|
items.POTATO_ID,
|
||||||
|
items.WHEAT_SEEDS_ID,
|
||||||
|
items.BEETROOT_SEEDS_ID,
|
||||||
|
})
|
||||||
|
return [self.gather_crop_states, self.cache_items_states]
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
if self.g.get('breaking'):
|
if self.g.get('breaking'):
|
||||||
self.g.game.cancel_break()
|
self.g.game.cancel_break()
|
||||||
|
if self.g.get('window'):
|
||||||
|
self.g.game.close_window()
|
||||||
self.g.path = []
|
self.g.path = []
|
||||||
self.g.look_at = None
|
self.g.look_at = None
|
||||||
self.gather_crop_states.state = self.gather_crop_states.idle
|
self.gather_crop_states.state = self.gather_crop_states.idle
|
||||||
|
self.cache_items_states.state = self.cache_items_states.idle
|
||||||
self.gather_crop_states.active_section_anchor = None
|
self.gather_crop_states.active_section_anchor = None
|
||||||
self.state = self.idle
|
self.state = self.idle
|
||||||
|
|||||||
+43
-17
@@ -13,7 +13,6 @@ from mosfet import utils
|
|||||||
from mosfet import path
|
from mosfet import path
|
||||||
from mosfet.info import blocks
|
from mosfet.info import blocks
|
||||||
from mosfet.info import items
|
from mosfet.info import items
|
||||||
from mosfet.info import mcdata
|
|
||||||
from mosfet.info import mobs
|
from mosfet.info import mobs
|
||||||
|
|
||||||
class CacheItemsStates:
|
class CacheItemsStates:
|
||||||
@@ -25,8 +24,12 @@ class CacheItemsStates:
|
|||||||
self.skip_items = []
|
self.skip_items = []
|
||||||
|
|
||||||
num_stacks = self.g.game.count_inventory_slots()
|
num_stacks = self.g.game.count_inventory_slots()
|
||||||
|
minimum = getattr(
|
||||||
|
self, 'minimum', self.g.get('minimum_cache_slots', 27))
|
||||||
print('Inventory amount:', num_stacks)
|
print('Inventory amount:', num_stacks)
|
||||||
if num_stacks >= self.g.minimum_cache_slots:
|
if num_stacks == 0:
|
||||||
|
self.state = self.cleanup
|
||||||
|
elif num_stacks >= minimum:
|
||||||
self.state = self.find_trapped_chests
|
self.state = self.find_trapped_chests
|
||||||
else:
|
else:
|
||||||
print('Aborting caching, not full')
|
print('Aborting caching, not full')
|
||||||
@@ -53,23 +56,30 @@ class CacheItemsStates:
|
|||||||
return
|
return
|
||||||
|
|
||||||
chest = self.trapped_chests[0]
|
chest = self.trapped_chests[0]
|
||||||
|
|
||||||
navpath = w.path_to_place_faked(p, chest)
|
|
||||||
print('navpath:', navpath)
|
|
||||||
|
|
||||||
if navpath:
|
|
||||||
self.g.path = navpath[:-1]
|
|
||||||
self.opening = self.g.path[-1]
|
|
||||||
self.area = chest
|
self.area = chest
|
||||||
|
|
||||||
|
for opening in w.find_cache_openings(chest):
|
||||||
|
navpath = w.path_to_place(p, opening)
|
||||||
|
if navpath:
|
||||||
|
print('navpath to trapped chest:', navpath)
|
||||||
|
self.opening = opening
|
||||||
|
self.g.path = navpath
|
||||||
self.state = self.going_to_trapped_chest
|
self.state = self.going_to_trapped_chest
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
self.trapped_chests.pop(0)
|
self.trapped_chests.pop(0)
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
def going_to_trapped_chest(self):
|
def going_to_trapped_chest(self):
|
||||||
if utils.pint(self.g.pos) == self.opening:
|
if utils.pint(self.g.pos) == self.opening:
|
||||||
self.g.look_at = self.area
|
self.g.look_at = self.area
|
||||||
|
self.wait_time = 0.5
|
||||||
|
self.state = self.turn_to_trapped_chest
|
||||||
|
|
||||||
|
def turn_to_trapped_chest(self):
|
||||||
|
self.g.look_at = self.area
|
||||||
|
self.wait_time -= utils.TICK
|
||||||
|
if self.wait_time <= 0:
|
||||||
self.state = self.open_chest
|
self.state = self.open_chest
|
||||||
|
|
||||||
def select_chest(self):
|
def select_chest(self):
|
||||||
@@ -84,7 +94,7 @@ class CacheItemsStates:
|
|||||||
w = self.g.world
|
w = self.g.world
|
||||||
p = utils.pint(self.g.pos)
|
p = utils.pint(self.g.pos)
|
||||||
|
|
||||||
for area in w.find_cache_areas(p, 100):
|
for area in w.find_cache_areas(p, 16):
|
||||||
print('Found area:', area)
|
print('Found area:', area)
|
||||||
if area not in self.bad_areas:
|
if area not in self.bad_areas:
|
||||||
break
|
break
|
||||||
@@ -112,10 +122,18 @@ class CacheItemsStates:
|
|||||||
def going_to_area(self):
|
def going_to_area(self):
|
||||||
if utils.pint(self.g.pos) == self.opening:
|
if utils.pint(self.g.pos) == self.opening:
|
||||||
self.g.look_at = self.area
|
self.g.look_at = self.area
|
||||||
|
self.wait_time = 0.5
|
||||||
|
self.state = self.turn_to_cache_spot
|
||||||
|
|
||||||
|
def turn_to_cache_spot(self):
|
||||||
|
self.g.look_at = self.area
|
||||||
|
self.wait_time -= utils.TICK
|
||||||
|
if self.wait_time <= 0:
|
||||||
self.state = self.place_chest
|
self.state = self.place_chest
|
||||||
|
|
||||||
def place_chest(self):
|
def place_chest(self):
|
||||||
self.g.game.place_block(self.area, BlockFace.TOP)
|
support = utils.padd(self.area, path.BLOCK_BELOW)
|
||||||
|
self.g.game.place_block(support, BlockFace.TOP)
|
||||||
self.state = self.open_chest
|
self.state = self.open_chest
|
||||||
|
|
||||||
def open_chest(self):
|
def open_chest(self):
|
||||||
@@ -139,14 +157,19 @@ class CacheItemsStates:
|
|||||||
print('Didnt get a window, aborting')
|
print('Didnt get a window, aborting')
|
||||||
self.state = self.cleanup
|
self.state = self.cleanup
|
||||||
return
|
return
|
||||||
if w.data.window_type != mcdata.SINGLE_CHEST:
|
if not w.container_slots:
|
||||||
print('Got wrong window, aborting')
|
print('Chest contents have not arrived, aborting')
|
||||||
|
self.g.game.close_window()
|
||||||
|
self.state = self.cleanup
|
||||||
|
return
|
||||||
|
if w.container_slots not in (27, 54):
|
||||||
|
print('Got non-chest window, aborting')
|
||||||
|
self.g.game.close_window()
|
||||||
self.state = self.cleanup
|
self.state = self.cleanup
|
||||||
return
|
return
|
||||||
|
|
||||||
w_info = mcdata.WINDOWS[w.data.window_type]
|
w_container_slots = range(w.container_slots)
|
||||||
w_inventory_slots = w_info.inventory
|
w_inventory_slots = range(w.container_slots, len(w.contents))
|
||||||
w_container_slots = w_info.container
|
|
||||||
|
|
||||||
used_slots = self.g.game.count_window_slots()
|
used_slots = self.g.game.count_window_slots()
|
||||||
print('used:', used_slots, 'total:', len(w_container_slots))
|
print('used:', used_slots, 'total:', len(w_container_slots))
|
||||||
@@ -204,6 +227,9 @@ class CacheItemsStates:
|
|||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self.g.look_at = None
|
self.g.look_at = None
|
||||||
self.state = self.done
|
self.state = self.done
|
||||||
|
if (self.g.get('job') and
|
||||||
|
getattr(self.g.job.state, '__name__', None) == 'cache_items'):
|
||||||
|
self.g.job.state = self.g.job.idle
|
||||||
|
|
||||||
def done(self):
|
def done(self):
|
||||||
# never gets ran, placeholder
|
# never gets ran, placeholder
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ from munch import Munch
|
|||||||
|
|
||||||
from minecraft.networking.packets import clientbound, serverbound
|
from minecraft.networking.packets import clientbound, serverbound
|
||||||
from minecraft.networking.modern import (
|
from minecraft.networking.modern import (
|
||||||
AddEntityPacket, ContainerSetContentPacket, ContainerSetSlotPacket,
|
AddEntityPacket, ContainerOpenPacket, ContainerSetContentPacket,
|
||||||
|
ContainerSetSlotPacket,
|
||||||
PaletteSection,
|
PaletteSection,
|
||||||
EntityPositionSyncPacket, MoveEntityPositionPacket,
|
EntityPositionSyncPacket, MoveEntityPositionPacket,
|
||||||
MoveEntityPositionRotationPacket, MoveEntityRotationPacket,
|
MoveEntityPositionRotationPacket, MoveEntityRotationPacket,
|
||||||
@@ -433,10 +434,13 @@ class InventoryManager:
|
|||||||
def __init__(self, global_state):
|
def __init__(self, global_state):
|
||||||
self.g = global_state
|
self.g = global_state
|
||||||
self.g.inv = self.g.get('inv') or {}
|
self.g.inv = self.g.get('inv') or {}
|
||||||
|
self.g.window = self.g.get('window')
|
||||||
|
self.g.item_lock = self.g.get('item_lock', False)
|
||||||
self.g.inventory_state_ids = self.g.get('inventory_state_ids') or {}
|
self.g.inventory_state_ids = self.g.get('inventory_state_ids') or {}
|
||||||
self.g.inventory_partial = False
|
self.g.inventory_partial = False
|
||||||
self.g.cursor = Slot(present=False)
|
self.g.cursor = Slot(present=False)
|
||||||
register = self.g.connection.register_packet_listener
|
register = self.g.connection.register_packet_listener
|
||||||
|
register(self.handle_open, ContainerOpenPacket)
|
||||||
register(self.handle_content, ContainerSetContentPacket)
|
register(self.handle_content, ContainerSetContentPacket)
|
||||||
register(self.handle_slot, ContainerSetSlotPacket)
|
register(self.handle_slot, ContainerSetSlotPacket)
|
||||||
register(self.handle_cursor, SetCursorItemPacket)
|
register(self.handle_cursor, SetCursorItemPacket)
|
||||||
@@ -449,6 +453,16 @@ class InventoryManager:
|
|||||||
removed_components=item.removed_components,
|
removed_components=item.removed_components,
|
||||||
components_unknown=item.components_unknown)
|
components_unknown=item.components_unknown)
|
||||||
|
|
||||||
|
def handle_open(self, packet):
|
||||||
|
self.g.window = Munch(
|
||||||
|
window_id=packet.window_id,
|
||||||
|
inventory_type=packet.inventory_type,
|
||||||
|
title=packet.title,
|
||||||
|
contents={},
|
||||||
|
container_slots=0,
|
||||||
|
state_id=self.g.inventory_state_ids.get(packet.window_id, 0),
|
||||||
|
)
|
||||||
|
|
||||||
def handle_content(self, packet):
|
def handle_content(self, packet):
|
||||||
slots = {index: self.to_slot(item)
|
slots = {index: self.to_slot(item)
|
||||||
for index, item in enumerate(packet.items)}
|
for index, item in enumerate(packet.items)}
|
||||||
@@ -457,9 +471,14 @@ class InventoryManager:
|
|||||||
self.g.inv = slots
|
self.g.inv = slots
|
||||||
self.g.inventory_partial = packet.partial
|
self.g.inventory_partial = packet.partial
|
||||||
self.g.cursor = self.to_slot(packet.carried_item)
|
self.g.cursor = self.to_slot(packet.carried_item)
|
||||||
elif self.g.get('window'):
|
elif (self.g.get('window') and
|
||||||
|
packet.container_id == self.g.window.window_id):
|
||||||
self.g.window.contents = slots
|
self.g.window.contents = slots
|
||||||
|
# Player inventory contributes 36 slots to chest menus.
|
||||||
|
self.g.window.container_slots = max(0, len(slots) - 36)
|
||||||
|
self.g.window.state_id = packet.state_id
|
||||||
self.g.cursor = self.to_slot(packet.carried_item)
|
self.g.cursor = self.to_slot(packet.carried_item)
|
||||||
|
self.g.item_lock = False
|
||||||
|
|
||||||
def handle_slot(self, packet):
|
def handle_slot(self, packet):
|
||||||
self.g.inventory_state_ids[packet.container_id] = packet.state_id
|
self.g.inventory_state_ids[packet.container_id] = packet.state_id
|
||||||
@@ -468,10 +487,19 @@ class InventoryManager:
|
|||||||
self.g.inventory_partial = True
|
self.g.inventory_partial = True
|
||||||
if packet.container_id == 0:
|
if packet.container_id == 0:
|
||||||
self.g.inv[packet.slot] = item
|
self.g.inv[packet.slot] = item
|
||||||
|
self.g.item_lock = False
|
||||||
elif packet.container_id == -1 and packet.slot == -1:
|
elif packet.container_id == -1 and packet.slot == -1:
|
||||||
self.g.cursor = item
|
self.g.cursor = item
|
||||||
elif self.g.get('window'):
|
elif (self.g.get('window') and
|
||||||
|
packet.container_id == self.g.window.window_id):
|
||||||
self.g.window.contents[packet.slot] = item
|
self.g.window.contents[packet.slot] = item
|
||||||
|
self.g.window.state_id = packet.state_id
|
||||||
|
offset = packet.slot - self.g.window.container_slots
|
||||||
|
if 0 <= offset < 27:
|
||||||
|
self.g.inv[9 + offset] = item
|
||||||
|
elif 27 <= offset < 36:
|
||||||
|
self.g.inv[36 + offset - 27] = item
|
||||||
|
self.g.item_lock = False
|
||||||
|
|
||||||
def handle_cursor(self, packet):
|
def handle_cursor(self, packet):
|
||||||
if packet.item.components_unknown:
|
if packet.item.components_unknown:
|
||||||
|
|||||||
+19
-1
@@ -182,7 +182,25 @@ class World:
|
|||||||
yield a
|
yield a
|
||||||
|
|
||||||
def find_cache_areas(self, center, distance):
|
def find_cache_areas(self, center, distance):
|
||||||
return self.find_bed_areas(center, distance)
|
"""Yield nearby, supported air blocks suitable for placing a chest."""
|
||||||
|
center = tuple(center)
|
||||||
|
for offset in utils.search_2d(distance):
|
||||||
|
if utils.hypot(offset[0], offset[2]) > distance:
|
||||||
|
continue
|
||||||
|
for dy in (0, 1, -1, 2, -2):
|
||||||
|
area = (center[0] + offset[0], center[1] + dy,
|
||||||
|
center[2] + offset[2])
|
||||||
|
if area == center:
|
||||||
|
continue
|
||||||
|
if self.block_at(*area) != blocks.AIR:
|
||||||
|
continue
|
||||||
|
if self.block_at(*utils.padd(area, path.BLOCK_ABOVE)) != blocks.AIR:
|
||||||
|
continue
|
||||||
|
support = self.block_at(*utils.padd(area, path.BLOCK_BELOW))
|
||||||
|
if (support is None or support in blocks.NON_SOLID_IDS or
|
||||||
|
support in blocks.AVOID_IDS):
|
||||||
|
continue
|
||||||
|
yield area
|
||||||
|
|
||||||
def sand_adjacent_safe(self, sand):
|
def sand_adjacent_safe(self, sand):
|
||||||
for direction in path.CHECK_DIRECTIONS:
|
for direction in path.CHECK_DIRECTIONS:
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from munch import Munch
|
||||||
|
|
||||||
|
from mosfet.info import items
|
||||||
|
from mosfet.job import JobStates
|
||||||
|
|
||||||
|
|
||||||
|
class TestCropCaching(unittest.TestCase):
|
||||||
|
def test_crop_farm_runs_silent_cache_machine_and_keeps_seeds(self):
|
||||||
|
old_wanted = items.WANTED_ITEMS
|
||||||
|
crop_state = object()
|
||||||
|
cache_state = Munch(silent=False)
|
||||||
|
jobs = JobStates.__new__(JobStates)
|
||||||
|
jobs.gather_crop_states = crop_state
|
||||||
|
jobs.cache_items_states = cache_state
|
||||||
|
|
||||||
|
try:
|
||||||
|
machines = JobStates.farm_crop(jobs)
|
||||||
|
self.assertEqual(machines, [crop_state, cache_state])
|
||||||
|
self.assertTrue(cache_state.silent)
|
||||||
|
self.assertTrue({
|
||||||
|
items.CARROT_ID, items.POTATO_ID,
|
||||||
|
items.WHEAT_SEEDS_ID, items.BEETROOT_SEEDS_ID,
|
||||||
|
}.issubset(items.WANTED_ITEMS))
|
||||||
|
finally:
|
||||||
|
items.WANTED_ITEMS = old_wanted
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
|
import io
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from munch import Munch
|
from munch import Munch
|
||||||
from minecraft.networking.modern import ClientTickEnd
|
from minecraft.networking.modern import ClientTickEnd, ContainerOpenPacket
|
||||||
from minecraft.networking.packets import PacketBuffer
|
from minecraft.networking.packets import PacketBuffer
|
||||||
|
|
||||||
from mosfet.game import ContainerClickDropPacket, Game
|
from mosfet.game import (
|
||||||
|
ContainerClickDropPacket, ContainerClickQuickMovePacket, Game,
|
||||||
|
)
|
||||||
from mosfet.protocol.types import Slot
|
from mosfet.protocol.types import Slot
|
||||||
|
|
||||||
|
|
||||||
@@ -27,6 +30,24 @@ class TestInventoryDrain(unittest.TestCase):
|
|||||||
self.assertEqual(payload.get_writable(), bytes.fromhex(
|
self.assertEqual(payload.get_writable(), bytes.fromhex(
|
||||||
'0007002d01040000'))
|
'0007002d01040000'))
|
||||||
|
|
||||||
|
def test_quick_move_packet_encodes_shift_click(self):
|
||||||
|
packet = ContainerClickQuickMovePacket(
|
||||||
|
container_id=3, state_id=7, slot=42)
|
||||||
|
payload = PacketBuffer()
|
||||||
|
packet.write_fields(payload)
|
||||||
|
|
||||||
|
self.assertEqual(packet.id, 0x12)
|
||||||
|
self.assertEqual(payload.get_writable(), bytes.fromhex(
|
||||||
|
'0307002a00010000'))
|
||||||
|
|
||||||
|
def test_open_window_packet_decodes_container_metadata(self):
|
||||||
|
# window id, menu type, empty anonymous-NBT title
|
||||||
|
packet = ContainerOpenPacket()
|
||||||
|
packet.read(io.BytesIO(bytes.fromhex('010200')))
|
||||||
|
self.assertEqual(packet.window_id, 1)
|
||||||
|
self.assertEqual(packet.inventory_type, 2)
|
||||||
|
self.assertIsNone(packet.title)
|
||||||
|
|
||||||
def test_drain_waits_for_authoritative_slot_update(self):
|
def test_drain_waits_for_authoritative_slot_update(self):
|
||||||
connection = FakeConnection()
|
connection = FakeConnection()
|
||||||
game = Game.__new__(Game)
|
game = Game.__new__(Game)
|
||||||
|
|||||||
Reference in New Issue
Block a user