Port and tune 26.3 wood farming

This commit is contained in:
Tanner
2026-11-05 16:36:10 +00:00
parent b89f0f6ed2
commit 4c27f5058e
15 changed files with 412 additions and 40 deletions
+2
View File
@@ -417,6 +417,7 @@ def start_automation(global_state, initialize=True):
g.break_time = 0.0 g.break_time = 0.0
g.item_lock = False g.item_lock = False
g.minimum_cache_slots = g.get('minimum_cache_slots', 27) g.minimum_cache_slots = g.get('minimum_cache_slots', 27)
g.maximum_supply_slots = g.get('maximum_supply_slots', 33)
g.draining = False g.draining = False
g.dumping = None g.dumping = None
g.dump_waiting = None g.dump_waiting = None
@@ -461,6 +462,7 @@ def start_automation(global_state, initialize=True):
g.setdefault('drain_error', None) g.setdefault('drain_error', None)
g.setdefault('item_lock', False) g.setdefault('item_lock', False)
g.setdefault('minimum_cache_slots', 27) g.setdefault('minimum_cache_slots', 27)
g.setdefault('maximum_supply_slots', 33)
g.setdefault('holding_slot', 0) g.setdefault('holding_slot', 0)
g.setdefault('holding_hand', 0) g.setdefault('holding_hand', 0)
g.setdefault('time', 0.0) g.setdefault('time', 0.0)
+3 -1
View File
@@ -71,7 +71,9 @@ class Commands:
} }
if ((command == 'gather' and data.lower().strip() != 'wood') or if ((command == 'gather' and data.lower().strip() != 'wood') or
command in unsupported or command in unsupported or
(command == 'farm' and not data.lower().startswith('crop'))): (command == 'farm' and
not data.lower().startswith('crop') and
data.lower().strip() != 'wood')):
reply = 'not migrated to Minecraft 26.3 yet' reply = 'not migrated to Minecraft 26.3 yet'
print('Reply:', reply) print('Reply:', reply)
self._send_reply(reply, private, reply_target) self._send_reply(reply, private, reply_target)
+29 -2
View File
@@ -2,6 +2,7 @@ import re
import time import time
import random import random
from itertools import count from itertools import count
from math import floor, isfinite
from munch import Munch from munch import Munch
from minecraft.networking.packets import Packet, clientbound, serverbound from minecraft.networking.packets import Packet, clientbound, serverbound
@@ -721,15 +722,41 @@ class Game(LegacyGame):
block_id = self.g.chunks.get_block_at(*location) block_id = self.g.chunks.get_block_at(*location)
if block_id is None or block_id == blocks.AIR: if block_id is None or block_id == blocks.AIR:
return False return False
holding = self.g.get('holding', 0)
held_slot = (45 if self.g.get('holding_hand', 0) == 1 else
36 + int(self.g.get('holding_slot', 0) or 0))
held_stack = self.g.get('inv', {}).get(held_slot)
enchantments = (getattr(held_stack, 'components', {}) or {}).get(
'enchantments', {})
in_water = False
on_ground = True
position = self.g.get('pos')
chunks = self.g.get('chunks')
if position is not None and chunks is not None:
x, y, z = (floor(position.x), floor(position.y),
floor(position.z))
support = chunks.get_block_at(x, floor(position.y - 0.2), z)
if support is not None:
on_ground = support not in blocks.NON_SOLID_IDS
feet = chunks.get_block_at(x, y, z)
eyes = chunks.get_block_at(x, floor(position.y + 1.62), z)
in_water = feet in blocks.WATER_IDS or eyes in blocks.WATER_IDS
duration = utils.break_time(
block_id, holding, in_water=in_water, on_ground=on_ground,
enchantments=enchantments, effects=self.g.get('effects', {}))
if not isfinite(duration):
return False
packet = PlayerActionPacket( packet = PlayerActionPacket(
action=0, location=location, face=1, action=0, location=location, face=1,
sequence=self._next_sequence()) sequence=self._next_sequence())
self._send_action(packet) self._send_action(packet)
self.g.breaking = location self.g.breaking = location
self.breaking_state = block_id self.breaking_state = block_id
hardness_time = utils.break_time(block_id, self.g.get('holding', 0))
now = time.monotonic() now = time.monotonic()
self.break_deadline = now + max(0.05, hardness_time) self.break_deadline = now + duration
self.animate() self.animate()
self.next_swing_at = now + self.ARM_SWING_INTERVAL self.next_swing_at = now + self.ARM_SWING_INTERVAL
return True return True
+9
View File
@@ -55,6 +55,15 @@ def get_id(name):
return ITEMS['minecraft:' + name]['protocol_id'] return ITEMS['minecraft:' + name]['protocol_id']
def get_sapling_id(tree_type):
"""Return the plantable sapling/propagule item for a tree species."""
for suffix in ('_sapling', '_propagule'):
item = ITEMS.get('minecraft:' + tree_type + suffix)
if item is not None:
return item['protocol_id']
return None
def get_name(idx): def get_name(idx):
return ITEM_NAMES[idx] return ITEM_NAMES[idx]
+28
View File
@@ -270,6 +270,11 @@ class JobStates(LegacyJobStates):
self.g = global_state self.g = global_state
self.gather_wood_states = gather_wood.GatherWoodStates(global_state) self.gather_wood_states = gather_wood.GatherWoodStates(global_state)
self.gather_crop_states = gather_crop.GatherCropStates(global_state) self.gather_crop_states = gather_crop.GatherCropStates(global_state)
self.grab_supplies_states = grab_supplies.GrabSuppliesStates(
global_state)
self.clear_leaves_states = clear_leaves.ClearLeavesStates(global_state)
self.plant_tree_states = plant_tree.PlantTreeStates(global_state)
self.grab_sapling_states = grab_sapling.GrabSaplingStates(global_state)
self.cache_items_states = cache_items.CacheItemsStates(global_state) self.cache_items_states = cache_items.CacheItemsStates(global_state)
self.sleep_with_bed_states = sleep_with_bed.SleepWithBedStates( self.sleep_with_bed_states = sleep_with_bed.SleepWithBedStates(
global_state) global_state)
@@ -278,6 +283,25 @@ class JobStates(LegacyJobStates):
global_state) global_state)
self.state = self.idle self.state = self.idle
def farm_wood(self):
self.sleep_with_bed_states.silent = True
self.cache_items_states.silent = True
self.grab_supplies_states.supplies = {
tuple(items.AXE_IDS): (1, 9),
}
items.set_needed(items.AXE_IDS)
items.set_wanted(items.SAPLING_IDS)
return [
self.grab_supplies_states,
self.gather_wood_states,
self.clear_leaves_states,
self.plant_tree_states,
self.grab_sapling_states,
self.sleep_with_bed_states,
self.eat_food_states,
self.cache_items_states,
]
def farm_crop(self): def farm_crop(self):
self.cache_items_states.silent = True self.cache_items_states.silent = True
self.sleep_with_bed_states.silent = True self.sleep_with_bed_states.silent = True
@@ -314,6 +338,10 @@ class JobStates(LegacyJobStates):
self.g.game.leave_bed() self.g.game.leave_bed()
self.gather_wood_states.state = self.gather_wood_states.idle self.gather_wood_states.state = self.gather_wood_states.idle
self.gather_crop_states.state = self.gather_crop_states.idle self.gather_crop_states.state = self.gather_crop_states.idle
self.grab_supplies_states.state = self.grab_supplies_states.idle
self.clear_leaves_states.state = self.clear_leaves_states.idle
self.plant_tree_states.state = self.plant_tree_states.idle
self.grab_sapling_states.state = self.grab_sapling_states.idle
self.cache_items_states.state = self.cache_items_states.idle self.cache_items_states.state = self.cache_items_states.idle
self.sleep_with_bed_states.state = self.sleep_with_bed_states.idle self.sleep_with_bed_states.state = self.sleep_with_bed_states.idle
self.sleep_with_bed_states.force_new_bed = False self.sleep_with_bed_states.force_new_bed = False
+14 -3
View File
@@ -17,6 +17,8 @@ from mosfet.info import mcdata
from mosfet.info import mobs from mosfet.info import mobs
class ClearLeavesStates: class ClearLeavesStates:
LEAF_BREAK_INTERVAL = 0.6
def idle(self): def idle(self):
return None return None
@@ -26,9 +28,13 @@ class ClearLeavesStates:
self.state = self.cleanup self.state = self.cleanup
return return
sapling_type = self.g.chopped_tree + '_sapling' sapling_item = items.get_sapling_id(self.g.chopped_tree)
sapling_item = items.get_id(sapling_type) if sapling_item is None:
print('No sapling for chopped tree, skipping leaf cleanup')
self.state = self.cleanup
return
num_saplings = self.g.game.count_items([sapling_item]) num_saplings = self.g.game.count_items([sapling_item])
sapling_type = items.get_name(sapling_item)
print('Have', num_saplings, sapling_type, 'in inventory') print('Have', num_saplings, sapling_type, 'in inventory')
if num_saplings > 8: if num_saplings > 8:
@@ -57,9 +63,13 @@ class ClearLeavesStates:
def break_leaves(self): def break_leaves(self):
if not self.g.breaking: if not self.g.breaking:
if self.leaves: if self.leaves:
now = time.monotonic()
if now < self.next_break_at:
return
leaf = self.leaves.pop(0) leaf = self.leaves.pop(0)
self.g.look_at = leaf self.g.look_at = leaf
self.g.game.break_block(leaf) if self.g.game.break_block(leaf):
self.next_break_at = now + self.LEAF_BREAK_INTERVAL
print('Breaking leaf', leaf) print('Breaking leaf', leaf)
else: else:
self.wait_time = 1 self.wait_time = 1
@@ -86,6 +96,7 @@ class ClearLeavesStates:
self.leaves = [] self.leaves = []
self.wait_time = 0 self.wait_time = 0
self.next_break_at = 0.0
def run(self): def run(self):
self.state() self.state()
+8 -2
View File
@@ -32,7 +32,13 @@ class GatherWoodStates:
for tree in w.find_trees(p, 100): for tree in w.find_trees(p, 100):
print('Found tree:', tree) print('Found tree:', tree)
if tree not in self.bad_trees: if tree in self.bad_trees:
continue
tree_type = blocks.BLOCKS[w.block_at(*tree)].replace('_log', '')
if items.get_sapling_id(tree_type) is None:
print('No plantable sapling for', tree_type, '- skipping')
self.bad_trees.append(tree)
continue
break break
else: # for else: # for
print('No good trees left, aborting.') print('No good trees left, aborting.')
@@ -40,7 +46,7 @@ class GatherWoodStates:
return return
self.tree = tree self.tree = tree
self.type = blocks.BLOCKS[w.block_at(*tree)].replace('_log', '') self.type = tree_type
print('Type:', self.type) print('Type:', self.type)
self.state = self.find_openings self.state = self.find_openings
+3 -7
View File
@@ -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 GrabSuppliesStates: class GrabSuppliesStates:
@@ -160,8 +159,8 @@ class GrabSuppliesStates:
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 w.get('container_slots', 0) != 27:
print('Got wrong window, aborting') print('Got wrong container size, aborting')
self.state = self.cleanup self.state = self.cleanup
return return
used_slots = self.g.game.count_inventory_slots() used_slots = self.g.game.count_inventory_slots()
@@ -173,10 +172,7 @@ class GrabSuppliesStates:
self.state = self.cleanup self.state = self.cleanup
return return
w_info = mcdata.WINDOWS[w.data.window_type] for slot_num in range(w.container_slots):
w_container_slots = w_info.container
for slot_num in w_container_slots:
if slot_num not in w.contents: if slot_num not in w.contents:
continue continue
+6 -2
View File
@@ -39,10 +39,14 @@ class PlantTreeStates:
def select_sapling(self): def select_sapling(self):
p = utils.pint(self.g.pos) p = utils.pint(self.g.pos)
sapling_type = self.g.chopped_tree + '_sapling' sapling_item = items.get_sapling_id(self.g.chopped_tree)
sapling_item = items.get_id(sapling_type) if sapling_item is None:
print('Aborting planting, no sapling for', self.g.chopped_tree)
self.state = self.cleanup
return
if self.g.game.select_item([sapling_item]): if self.g.game.select_item([sapling_item]):
sapling_type = items.get_name(sapling_item)
self.g.look_at = utils.padd(p, path.BLOCK_BELOW) self.g.look_at = utils.padd(p, path.BLOCK_BELOW)
self.state = self.wait_select self.state = self.wait_select
self.wait_time = 1 self.wait_time = 1
+10 -1
View File
@@ -5,7 +5,7 @@ from munch import Munch
from minecraft.networking.packets import clientbound, serverbound from minecraft.networking.packets import clientbound, serverbound
from minecraft.networking.modern import ( from minecraft.networking.modern import (
AddEntityPacket, ContainerOpenPacket, ContainerSetContentPacket, AddEntityPacket, ContainerOpenPacket, ContainerSetContentPacket,
ContainerSetSlotPacket, ContainerSetSlotPacket, SetEntityDataPacket,
PaletteSection, PaletteSection,
EntityPositionSyncPacket, MoveEntityPositionPacket, EntityPositionSyncPacket, MoveEntityPositionPacket,
MoveEntityPositionRotationPacket, MoveEntityRotationPacket, MoveEntityPositionRotationPacket, MoveEntityRotationPacket,
@@ -347,6 +347,7 @@ class EntityManager:
register = self.g.connection.register_packet_listener register = self.g.connection.register_packet_listener
for packet_type, handler in ( for packet_type, handler in (
(AddEntityPacket, self.handle_add_entity), (AddEntityPacket, self.handle_add_entity),
(SetEntityDataPacket, self.handle_entity_data),
(RemoveEntitiesPacket, self.handle_remove_entities), (RemoveEntitiesPacket, self.handle_remove_entities),
(MoveEntityPositionPacket, self.handle_move), (MoveEntityPositionPacket, self.handle_move),
(MoveEntityPositionRotationPacket, self.handle_move), (MoveEntityPositionRotationPacket, self.handle_move),
@@ -378,6 +379,14 @@ class EntityManager:
type=packet.entity_type_id, yaw=packet.yaw, type=packet.entity_type_id, yaw=packet.yaw,
pitch=packet.pitch) pitch=packet.pitch)
def handle_entity_data(self, packet):
obj = self.g.objects.get(packet.entity_id)
if obj is None or not packet.item_stacks:
return
stack = packet.item_stacks[-1]
obj.item_id = stack.item_id if stack.present else None
obj.item_count = stack.item_count if stack.present else 0
def _entity(self, entity_id): def _entity(self, entity_id):
for collection in (self.g.players, self.g.mobs, self.g.objects): for collection in (self.g.players, self.g.mobs, self.g.objects):
entity = collection.get(entity_id) entity = collection.get(entity_id)
+34 -19
View File
@@ -82,33 +82,48 @@ def diffrange(n):
sign = 1 if n >= 0 else -1 sign = 1 if n >= 0 else -1
return range(0, n+sign, sign) return range(0, n+sign, sign)
def break_time(block_id, held_item=0, in_water=False, on_ground=True, enchantments=[], effects={}): def break_time(block_id, held_item=0, in_water=False, on_ground=True,
# from PrismarineJS/prismarine-block enchantments=None, effects=None):
"""Return vanilla block-breaking duration rounded to whole game ticks."""
held_item = str(held_item) held_item = str(held_item)
block_data = blocks.get(block_id) block_data = blocks.get(block_id)
hardness = block_data.get('hardness', 0)
if hardness < 0:
return float('inf')
can_harvest = 'harvestTools' not in block_data or str(held_item) in block_data['harvestTools'] can_harvest = (
'harvestTools' not in block_data or
held_item in block_data['harvestTools'])
material = block_data.get('material', 'n/a') material = block_data.get('material', 'n/a')
tool_multipliers = mcdata.mcd.materials.get(material, []) tool_multipliers = mcdata.mcd.materials.get(material, {})
is_best_tool = held_item in tool_multipliers speed = tool_multipliers.get(held_item, 1.0)
time = block_data['hardness']
if can_harvest: enchantments = enchantments or {}
time *= 1.5 if speed > 1:
else: efficiency = int(enchantments.get(
time *= 5.0 'efficiency', enchantments.get('minecraft:efficiency', 0)) or 0)
if efficiency > 0:
speed += efficiency * efficiency + 1
if is_best_tool: effects = effects or {}
speed_multiplier = tool_multipliers[held_item] haste = int(effects.get('haste', effects.get('minecraft:haste', 0)) or 0)
# TODO: calc efficiency, haste, mining fatigue if haste >= 0 and ('haste' in effects or 'minecraft:haste' in effects):
else: speed *= 1 + 0.2 * (haste + 1)
speed_multiplier = 1.0 fatigue = int(effects.get(
'mining_fatigue', effects.get('minecraft:mining_fatigue', -1)))
if fatigue >= 0:
speed *= (0.3, 0.09, 0.0027, 0.00081, 0.000243)[min(fatigue, 4)]
time /= speed_multiplier duration = hardness * (1.5 if can_harvest else 5.0) / speed
if in_water: time *= 5.0 if in_water:
if not on_ground: time *= 5.0 duration *= 5.0
if not on_ground:
duration *= 5.0
return time # Vanilla advances breaking progress in 1/20-second ticks; clients cannot
# finish between ticks. Round up, while avoiding floating-point over-round.
ticks = max(1, ceil(duration / TICK - 1e-9))
return ticks * TICK
def search_2d(distance=0): def search_2d(distance=0):
def get_neighbors(x,y,z): def get_neighbors(x,y,z):
+69
View File
@@ -0,0 +1,69 @@
import math
import unittest
from unittest.mock import patch
from munch import Munch
from mosfet import utils
from mosfet.game import Game
from mosfet.info import blocks, items
class BlockBreakTimingTests(unittest.TestCase):
def test_leaf_time_uses_actual_tool_speed_and_game_ticks(self):
oak_leaves = blocks.MCD_BLOCKS['oak_leaves']['minStateId']
oak_log = items.get_id('oak_log')
wooden_hoe = items.get_id('wooden_hoe')
self.assertAlmostEqual(utils.break_time(oak_leaves, oak_log), 0.3)
self.assertAlmostEqual(utils.break_time(oak_leaves, wooden_hoe), 0.15)
self.assertAlmostEqual(
utils.break_time(oak_leaves, oak_log, in_water=True), 1.5)
self.assertAlmostEqual(
utils.break_time(oak_leaves, oak_log, on_ground=False), 1.5)
def test_efficiency_haste_and_fatigue_affect_break_ticks(self):
oak_leaves = blocks.MCD_BLOCKS['oak_leaves']['minStateId']
wooden_hoe = items.get_id('wooden_hoe')
self.assertAlmostEqual(utils.break_time(
oak_leaves, wooden_hoe, enchantments={'efficiency': 1}), 0.1)
self.assertAlmostEqual(utils.break_time(
oak_leaves, wooden_hoe, effects={'haste': 0}), 0.15)
self.assertAlmostEqual(utils.break_time(
oak_leaves, wooden_hoe,
effects={'mining_fatigue': 0}), 0.5)
def test_unbreakable_blocks_have_no_completion_deadline(self):
bedrock = blocks.MCD_BLOCKS['bedrock']['minStateId']
self.assertTrue(math.isinf(utils.break_time(bedrock)))
def test_game_sends_finish_after_calculated_tick_duration(self):
class RecordingConnection:
def __init__(self):
self.packets = []
def write_packet(self, packet, force=False):
self.packets.append(packet)
class FakeChunks:
@staticmethod
def get_block_at(*_position):
return blocks.MCD_BLOCKS['oak_leaves']['minStateId']
connection = RecordingConnection()
game = object.__new__(Game)
game.g = Munch(
connection=connection, chunks=FakeChunks(), inv={},
holding=items.get_id('oak_log'), holding_hand=0,
holding_slot=0)
game.sequence = 0
game.break_deadline = None
game.breaking_state = None
game.next_swing_at = 0.0
with patch('mosfet.game.time.monotonic', return_value=100.0):
self.assertTrue(game.break_block((0, 64, 0)))
self.assertAlmostEqual(game.break_deadline, 100.3)
if __name__ == '__main__':
unittest.main()
+37
View File
@@ -0,0 +1,37 @@
import unittest
from unittest.mock import patch
from munch import Munch
from mosfet.jobs.clear_leaves import ClearLeavesStates
class ClearLeavesTests(unittest.TestCase):
def test_spaces_leaf_breaks_at_configured_interval(self):
class FakeGame:
def __init__(self):
self.broken = []
def break_block(self, location):
self.broken.append(location)
return True
game = FakeGame()
state = ClearLeavesStates(Munch(
breaking=False, game=game, look_at=None))
state.leaves = [(0, 64, 0), (1, 64, 0)]
with patch('mosfet.jobs.clear_leaves.time.monotonic', return_value=10):
state.break_leaves()
self.assertEqual(game.broken, [(0, 64, 0)])
with patch('mosfet.jobs.clear_leaves.time.monotonic', return_value=10.59):
state.break_leaves()
self.assertEqual(game.broken, [(0, 64, 0)])
with patch('mosfet.jobs.clear_leaves.time.monotonic', return_value=10.6):
state.break_leaves()
self.assertEqual(game.broken, [(0, 64, 0), (1, 64, 0)])
if __name__ == '__main__':
unittest.main()
+99 -2
View File
@@ -1,9 +1,15 @@
import unittest import unittest
from unittest.mock import patch
from munch import Munch from munch import Munch
from mosfet.bot import start_automation
from mosfet.commands import Commands from mosfet.commands import Commands
from mosfet.info import blocks, items from mosfet.info import blocks, items
from mosfet.job import JobStates from mosfet.job import JobStates
from mosfet.jobs.gather_wood import GatherWoodStates
from mosfet.jobs.grab_supplies import GrabSuppliesStates
from mosfet.protocol.types import Slot
from mosfet.world import World from mosfet.world import World
@@ -21,6 +27,27 @@ class GatherWoodPortTests(unittest.TestCase):
self.assertTrue(blocks.LOG_IDS) self.assertTrue(blocks.LOG_IDS)
self.assertTrue(blocks.LOG_IDS.issubset(blocks.INDEXED_IDS)) self.assertTrue(blocks.LOG_IDS.issubset(blocks.INDEXED_IDS))
def test_mangrove_uses_its_26_3_propagule_and_unplantable_logs_are_skipped(self):
self.assertEqual(items.get_sapling_id('mangrove'),
items.get_id('mangrove_propagule'))
self.assertIsNone(items.get_sapling_id('crimson'))
class FakeWorld:
def find_trees(self, _position, _distance):
return [(0, 64, 0), (3, 64, 0)]
def block_at(self, x, y, z):
return (blocks.MCD_BLOCKS['crimson_stem']['minStateId']
if x == 0 else
blocks.MCD_BLOCKS['mangrove_log']['minStateId'])
state = GatherWoodStates(
Munch(world=FakeWorld(), pos=(0, 64, 0)))
state.find_new_tree()
self.assertEqual(state.tree, (3, 64, 0))
self.assertEqual(state.type, 'mangrove')
self.assertEqual(state.bad_trees, [(0, 64, 0)])
def test_tree_search_finds_indexed_26_3_log_states(self): def test_tree_search_finds_indexed_26_3_log_states(self):
oak_log = blocks.MCD_BLOCKS['oak_log']['minStateId'] oak_log = blocks.MCD_BLOCKS['oak_log']['minStateId']
oak_leaves = blocks.MCD_BLOCKS['oak_leaves']['minStateId'] oak_leaves = blocks.MCD_BLOCKS['oak_leaves']['minStateId']
@@ -54,7 +81,76 @@ class GatherWoodPortTests(unittest.TestCase):
self.assertIs(job.gather_wood_states.state.__func__, self.assertIs(job.gather_wood_states.state.__func__,
job.gather_wood_states.idle.__func__) job.gather_wood_states.idle.__func__)
def test_gather_wood_command_is_enabled_but_farm_wood_is_not(self): def test_grab_supplies_reads_protocol_777_container_shape(self):
axe_id = next(iter(items.AXE_IDS))
axe = Slot(present=True, item_id=axe_id, item_count=1)
clicks = []
class FakeGame:
def count_inventory_slots(self):
return 10
def click_window(self, *args):
clicks.append(args)
g = Munch(
item_lock=False,
window=Munch(container_slots=27, contents={0: axe}),
maximum_supply_slots=33,
game=FakeGame(),
)
state = GrabSuppliesStates(g)
state.target_items = {axe_id}
state.maximum_stacks = 9
state.grab_items()
self.assertTrue(g.item_lock)
self.assertEqual(state.count, 1)
self.assertEqual(clicks, [(0, 0, 1, axe)])
def test_modern_automation_sets_supply_inventory_limit(self):
g = Munch()
with patch('mosfet.bot.threading.Thread') as thread:
start_automation(g, initialize=False)
self.assertEqual(g.maximum_supply_slots, 33)
thread.return_value.start.assert_called_once()
def test_farm_wood_job_keeps_legacy_machine_sequence(self):
job = JobStates(Munch())
try:
self.assertEqual(job.farm_wood(), [
job.grab_supplies_states,
job.gather_wood_states,
job.clear_leaves_states,
job.plant_tree_states,
job.grab_sapling_states,
job.sleep_with_bed_states,
job.eat_food_states,
job.cache_items_states,
])
self.assertEqual(job.grab_supplies_states.supplies, {
tuple(items.AXE_IDS): (1, 9),
})
self.assertTrue(job.sleep_with_bed_states.silent)
self.assertTrue(job.cache_items_states.silent)
self.assertTrue(items.AXE_IDS.issubset(items.NEEDED_ITEMS))
self.assertTrue(items.SAPLING_IDS.issubset(items.WANTED_ITEMS))
job.grab_supplies_states.state = job.grab_supplies_states.find_barrels
job.clear_leaves_states.state = job.clear_leaves_states.find_leaves
job.plant_tree_states.state = job.plant_tree_states.check_feet
job.grab_sapling_states.state = job.grab_sapling_states.find_saplings
job.stop()
for machine in (job.grab_supplies_states,
job.clear_leaves_states,
job.plant_tree_states,
job.grab_sapling_states):
self.assertIs(machine.state.__func__, machine.idle.__func__)
finally:
items.set_needed(set())
items.set_wanted(set())
def test_gather_and_farm_wood_commands_are_enabled(self):
class FakeChat: class FakeChat:
def __init__(self): def __init__(self):
self.sent = [] self.sent = []
@@ -75,7 +171,8 @@ class GatherWoodPortTests(unittest.TestCase):
chat.sent.clear() chat.sent.clear()
Commands(g).handle_chat(('CHAT', 'tester', 'tester: 1farm wood')) Commands(g).handle_chat(('CHAT', 'tester', 'tester: 1farm wood'))
self.assertEqual(chat.sent, ['not migrated to Minecraft 26.3 yet']) self.assertEqual(chat.sent, ['ok'])
self.assertIs(job.state.__func__, job.farm_wood.__func__)
if __name__ == '__main__': if __name__ == '__main__':
+60
View File
@@ -0,0 +1,60 @@
import io
import unittest
from munch import Munch
from minecraft.networking.modern import (
ModernPlayingReactor, SetEntityDataPacket,
)
from mosfet.info import items
from mosfet.protocol.managers import EntityManager
from mosfet.world import World
def encode_varint(value):
encoded = bytearray()
while value & ~0x7F:
encoded.append((value & 0x7F) | 0x80)
value >>= 7
encoded.append(value)
return bytes(encoded)
class SaplingEntityTests(unittest.TestCase):
def test_protocol_777_item_metadata_decodes_item_stack(self):
sapling_id = items.get_id('oak_sapling')
payload = (
encode_varint(42) +
bytes((8,)) + encode_varint(7) +
encode_varint(1) + encode_varint(sapling_id) +
encode_varint(0) + encode_varint(0) +
bytes((0xFF,))
)
packet = SetEntityDataPacket()
packet.read(io.BytesIO(payload))
self.assertEqual(packet.entity_id, 42)
self.assertEqual(len(packet.item_stacks), 1)
self.assertEqual(packet.item_stacks[0].item_id, sapling_id)
self.assertIn(SetEntityDataPacket,
ModernPlayingReactor.get_clientbound_packets(None))
def test_item_metadata_makes_dropped_sapling_searchable(self):
sapling_id = items.get_id('oak_sapling')
entity = Munch(entity_id=42, x=1.0, y=65.0, z=2.0)
manager = object.__new__(EntityManager)
manager.g = Munch(objects={42: entity})
packet = Munch(
entity_id=42,
item_stacks=[Munch(present=True, item_id=sapling_id,
item_count=1)],
)
manager.handle_entity_data(packet)
found = World(Munch(objects={42: entity})).find_objects(
items.SAPLING_IDS)
self.assertEqual(found, [entity])
self.assertEqual(entity.item_count, 1)
if __name__ == '__main__':
unittest.main()