Port and tune 26.3 wood farming
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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):
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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__':
|
||||||
|
|||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user