Files
minecraft-bot/mosfet/jobs/gather_crop.py
T

260 rivejä
9.2 KiB
Python

import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GatherCropStates:
CROPS_PER_BATCH = 16
def idle(self):
return None
def init(self):
self.state = self.find_new_crop
def _select_crop(self, candidates, crop_types, center):
"""Stay with a connected same-species patch, nearest patch first."""
adjacent_offsets = ((1, 0, 0), (-1, 0, 0),
(0, 0, 1), (0, 0, -1))
remaining = set(crop_types)
component_for = {}
component_sizes = {}
component_id = 0
while remaining:
seed = remaining.pop()
species = crop_types[seed]
stack = [seed]
component = [seed]
component_for[seed] = component_id
while stack:
x, y, z = stack.pop()
for dx, dy, dz in adjacent_offsets:
adjacent = (x + dx, y + dy, z + dz)
if (adjacent in remaining and
crop_types[adjacent] == species):
remaining.remove(adjacent)
component_for[adjacent] = component_id
component.append(adjacent)
stack.append(adjacent)
component_sizes[component_id] = len(component)
component_id += 1
eligible = [position for position in candidates
if position not in self.bad_crops and
position in component_for]
if not eligible:
return None, 0
active_component = component_for.get(self.active_section_anchor)
active_candidates = [position for position in eligible
if component_for[position] == active_component]
if active_candidates:
section_candidates = active_candidates
else:
# Begin with the nearest available patch. Once selected, keep its
# connected component active until no mature crops remain there.
first_crop = min(eligible, key=lambda position: (
utils.phyp(center, position), position))
selected_component = component_for[first_crop]
section_candidates = [
position for position in eligible
if component_for[position] == selected_component
]
crop = min(section_candidates, key=lambda position: (
utils.phyp(center, position), position))
self.active_section_anchor = crop
return crop, component_sizes[component_for[crop]]
def find_new_crop(self):
print('Finding new crop...')
w = self.g.world
p = utils.pint(self.g.pos)
mature_crops = [
blocks.MATURE_WHEAT_ID,
blocks.MATURE_POTATO_ID,
blocks.MATURE_CARROT_ID,
blocks.MATURE_BEETROOT_ID,
]
candidates = w.filter_work_positions(
w.find_blocks_indexed(p, mature_crops, 50))
crop_ids = (blocks.WHEAT_IDS | blocks.POTATO_IDS |
blocks.CARROT_IDS | blocks.BEETROOT_IDS)
crop_positions = w.filter_work_positions(
w.find_blocks_indexed(p, crop_ids, 50))
crop_types = {
position: blocks.BLOCKS.get(w.block_at(*position))
for position in crop_positions
}
crop, section_size = self._select_crop(candidates, crop_types, p)
if crop is None:
print('No good crops left; retrying after a short wait.')
self.retry_after = time.monotonic() + 15.0
self.state = self.wait_for_crops
return
print('Found crop:', crop,
'(same-species section size={})'.format(section_size))
self.crop = crop
self.type_id = w.block_at(*crop)
self.state = self.nav_to_crop
def wait_for_crops(self):
if time.monotonic() >= self.retry_after:
self.state = self.find_new_crop
def nav_to_crop(self):
w = self.g.world
p = utils.pint(self.g.pos)
navpath = w.path_to_place(p, self.crop)
if navpath:
print('Going to crop', self.crop)
self.g.path = navpath
self.g.look_at = self.crop
self.state = self.going_to_crop
else:
print('Cant get to it, blacklisting')
time.sleep(0.1)
self.bad_crops.append(self.crop)
self.state = self.find_new_crop
def going_to_crop(self):
if utils.pint(self.g.pos) == self.crop:
print('At the crop')
self.state = self.break_crop
def break_crop(self):
print('Requesting crop break at {}: block={}, mode={}, held={}'.format(
self.crop, self.g.world.block_at(*self.crop),
self.g.get('game_mode'), self.g.get('holding')))
if self.g.game.break_block(self.crop):
print('Started breaking crop {} (state {})'.format(
self.crop, self.type_id))
self.break_deadline = time.time() + 5.0
self.state = self.wait_break
else:
print('Break request skipped; current block is',
self.g.world.block_at(*self.crop))
self.bad_crops.append(self.crop)
self.state = self.find_new_crop
def wait_break(self):
current_state = self.g.world.block_at(*self.crop)
if current_state != self.type_id:
print('Crop break confirmed at {}: {} -> {}'.format(
self.crop, self.type_id, current_state))
self.seed_deadline = time.monotonic() + 8.0
self.seed_wait_logged = False
self.state = self.select_seed
elif time.time() > self.break_deadline:
print('Crop break timed out at {}: expected state {}, server still has {}'.format(
self.crop, self.type_id, current_state))
print('Crop did not break; blacklisting', self.crop)
self.bad_crops.append(self.crop)
self.state = self.find_new_crop
def select_seed(self):
p = utils.pint(self.g.pos)
crop_seeds = {
blocks.MATURE_WHEAT_ID: items.WHEAT_SEEDS_ID,
blocks.MATURE_POTATO_ID: items.POTATO_ID,
blocks.MATURE_CARROT_ID: items.CARROT_ID,
blocks.MATURE_BEETROOT_ID: items.BEETROOT_SEEDS_ID,
}
self.target_seed = crop_seeds[self.type_id]
if self.g.game.select_item([self.target_seed]):
self.seed_deadline = None
self.state = self.wait_select
else:
if (self.seed_deadline is not None and
time.monotonic() > self.seed_deadline):
print('No seed collected for crop; skipping', self.crop)
self.bad_crops.append(self.crop)
self.seed_deadline = None
self.state = self.find_new_crop
elif not getattr(self, 'seed_wait_logged', False):
print('Waiting for crop seed {} in inventory/hotbar'.format(
self.target_seed))
self.seed_wait_logged = True
return
def wait_select(self):
if self.target_seed != self.g.holding:
return
else:
self.state = self.place_crop
def place_crop(self):
p = utils.padd(self.crop, path.BLOCK_BELOW)
self.place_deadline = time.time() + 3.0
self.g.game.place_block(p, BlockFace.TOP)
print('Placed crop')
self.state = self.wait_place
def wait_place(self):
w = self.g.world
current_state = w.block_at(*self.crop)
crop_name = blocks.BLOCKS.get(self.type_id)
replanted = (current_state not in (None, blocks.AIR) and
blocks.BLOCKS.get(current_state) == crop_name and
current_state != self.type_id)
if replanted:
self.completed_crops += 1
if self.completed_crops >= self.CROPS_PER_BATCH:
print('Completed {} crop harvests; running maintenance'.format(
self.completed_crops))
self.completed_crops = 0
self.state = self.cleanup
else:
self.state = self.find_new_crop
elif time.time() > getattr(self, 'place_deadline', 0):
print('Crop did not replant; blacklisting', self.crop)
self.bad_crops.append(self.crop)
self.state = self.find_new_crop
def cleanup(self):
self.g.look_at = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.crop = None
self.type_id = None
self.target_seed = None
self.seed_deadline = None
self.seed_wait_logged = False
self.active_section_anchor = None
self.retry_after = 0.0
self.bad_crops = []
self.completed_crops = 0
def run(self):
self.state()