Port nether wart farming to 26.3
This commit is contained in:
+2
-1
@@ -62,7 +62,8 @@ FARMING_PITCH = 45.0
|
|||||||
|
|
||||||
def pitch_lock_for_job(job_state):
|
def pitch_lock_for_job(job_state):
|
||||||
state = getattr(job_state, 'state', None) if job_state else None
|
state = getattr(job_state, 'state', None) if job_state else None
|
||||||
return (FARMING_PITCH if getattr(state, '__name__', None) == 'farm_crop'
|
return (FARMING_PITCH
|
||||||
|
if getattr(state, '__name__', None) in ('farm_crop', 'farm_wart')
|
||||||
else None)
|
else None)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -73,7 +73,7 @@ class Commands:
|
|||||||
command in unsupported or
|
command in unsupported or
|
||||||
(command == 'farm' and
|
(command == 'farm' and
|
||||||
not data.lower().startswith('crop') and
|
not data.lower().startswith('crop') and
|
||||||
data.lower().strip() != 'wood')):
|
data.lower().strip() not in ('wood', 'wart'))):
|
||||||
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)
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ SAPLINGS = sorted(name for name in MCD_BLOCKS
|
|||||||
if name.endswith('_sapling') or name.endswith('_propagule'))
|
if name.endswith('_sapling') or name.endswith('_propagule'))
|
||||||
BEDS = sorted(name for name in MCD_BLOCKS if name.endswith('_bed'))
|
BEDS = sorted(name for name in MCD_BLOCKS if name.endswith('_bed'))
|
||||||
INDEXED = ['chest', 'trapped_chest', 'emerald_block', 'barrel'] + BEDS + [
|
INDEXED = ['chest', 'trapped_chest', 'emerald_block', 'barrel'] + BEDS + [
|
||||||
'wheat', 'potatoes', 'carrots', 'beetroots',
|
'wheat', 'potatoes', 'carrots', 'beetroots', 'nether_wart',
|
||||||
] + LOGS
|
] + LOGS
|
||||||
|
|
||||||
LOG_IDS = get_set(LOGS)
|
LOG_IDS = get_set(LOGS)
|
||||||
|
|||||||
+13
-1
@@ -270,11 +270,12 @@ class LegacyJobStates:
|
|||||||
|
|
||||||
|
|
||||||
class JobStates(LegacyJobStates):
|
class JobStates(LegacyJobStates):
|
||||||
"""26.3 facade for migrated gather-wood and crop workflows."""
|
"""26.3 facade for migrated gathering and farming workflows."""
|
||||||
def __init__(self, global_state):
|
def __init__(self, global_state):
|
||||||
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.gather_wart_states = gather_wart.GatherWartStates(global_state)
|
||||||
self.grab_supplies_states = grab_supplies.GrabSuppliesStates(
|
self.grab_supplies_states = grab_supplies.GrabSuppliesStates(
|
||||||
global_state)
|
global_state)
|
||||||
self.clear_leaves_states = clear_leaves.ClearLeavesStates(global_state)
|
self.clear_leaves_states = clear_leaves.ClearLeavesStates(global_state)
|
||||||
@@ -308,6 +309,17 @@ class JobStates(LegacyJobStates):
|
|||||||
self.cache_items_states,
|
self.cache_items_states,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def farm_wart(self):
|
||||||
|
self.sleep_with_bed_states.silent = True
|
||||||
|
self.cache_items_states.silent = True
|
||||||
|
items.set_wanted({items.NETHERWART_ID})
|
||||||
|
return [
|
||||||
|
self.gather_wart_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
|
||||||
|
|||||||
+72
-52
@@ -1,52 +1,52 @@
|
|||||||
import re
|
|
||||||
import time
|
import time
|
||||||
import importlib
|
|
||||||
import random
|
|
||||||
from itertools import count
|
|
||||||
from math import floor
|
|
||||||
|
|
||||||
from minecraft.networking.types import BlockFace
|
from minecraft.networking.types import BlockFace
|
||||||
|
|
||||||
from mosfet.protocol.managers import ChunkNotLoadedException
|
|
||||||
|
|
||||||
from mosfet import utils
|
|
||||||
from mosfet import path
|
from mosfet import path
|
||||||
|
from mosfet import utils
|
||||||
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
|
|
||||||
|
|
||||||
class GatherWartStates:
|
class GatherWartStates:
|
||||||
|
BREAK_TIMEOUT = 5.0
|
||||||
|
ITEM_TIMEOUT = 8.0
|
||||||
|
PLACE_TIMEOUT = 3.0
|
||||||
|
|
||||||
def idle(self):
|
def idle(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
self.state = self.find_new_wart
|
self.state = self.find_new_wart
|
||||||
|
|
||||||
|
def _blacklist_wart(self):
|
||||||
|
if self.wart is not None and self.wart not in self.bad_warts:
|
||||||
|
self.bad_warts.append(self.wart)
|
||||||
|
|
||||||
def find_new_wart(self):
|
def find_new_wart(self):
|
||||||
print('Finding new wart...')
|
print('Finding new wart...')
|
||||||
w = self.g.world
|
world = self.g.world
|
||||||
p = utils.pint(self.g.pos)
|
center = utils.pint(self.g.pos)
|
||||||
|
|
||||||
mature_wart = max(blocks.NETHERWART_IDS)
|
mature_wart = max(blocks.NETHERWART_IDS)
|
||||||
for wart in w.find_blocks_3d(p, [mature_wart], 50, 20, True):
|
candidates = world.find_blocks_indexed(
|
||||||
if (wart not in self.bad_warts and
|
center, {mature_wart}, 50)
|
||||||
w.work_position_allowed(wart)):
|
candidates = world.filter_work_positions(candidates)
|
||||||
break
|
wart = next((position for position in candidates
|
||||||
else: # for
|
if position not in self.bad_warts), None)
|
||||||
|
if wart is None:
|
||||||
print('No good warts left, aborting.')
|
print('No good warts left, aborting.')
|
||||||
self.state = self.cleanup
|
self.state = self.cleanup
|
||||||
return
|
return
|
||||||
|
|
||||||
print('Found wart:', wart)
|
print('Found wart:', wart)
|
||||||
self.wart = wart
|
self.wart = wart
|
||||||
|
self.type_id = mature_wart
|
||||||
self.state = self.nav_to_wart
|
self.state = self.nav_to_wart
|
||||||
|
|
||||||
def nav_to_wart(self):
|
def nav_to_wart(self):
|
||||||
w = self.g.world
|
world = self.g.world
|
||||||
p = utils.pint(self.g.pos)
|
position = utils.pint(self.g.pos)
|
||||||
|
navpath = world.path_to_place(position, self.wart)
|
||||||
navpath = w.path_to_place(p, self.wart)
|
|
||||||
|
|
||||||
if navpath:
|
if navpath:
|
||||||
print('Going to wart', self.wart)
|
print('Going to wart', self.wart)
|
||||||
@@ -55,8 +55,7 @@ class GatherWartStates:
|
|||||||
self.state = self.going_to_wart
|
self.state = self.going_to_wart
|
||||||
else:
|
else:
|
||||||
print('Cant get to it, blacklisting')
|
print('Cant get to it, blacklisting')
|
||||||
time.sleep(0.1)
|
self._blacklist_wart()
|
||||||
self.bad_warts.append(wart)
|
|
||||||
self.state = self.find_new_wart
|
self.state = self.find_new_wart
|
||||||
|
|
||||||
def going_to_wart(self):
|
def going_to_wart(self):
|
||||||
@@ -65,63 +64,84 @@ class GatherWartStates:
|
|||||||
self.state = self.break_wart
|
self.state = self.break_wart
|
||||||
|
|
||||||
def break_wart(self):
|
def break_wart(self):
|
||||||
self.g.game.break_block(self.wart)
|
current = self.g.world.block_at(*self.wart)
|
||||||
self.wait_time = 0.5
|
if current != self.type_id:
|
||||||
self.state = self.wait
|
print('Wart changed before break; searching again')
|
||||||
|
self._blacklist_wart()
|
||||||
|
self.state = self.find_new_wart
|
||||||
|
return
|
||||||
|
|
||||||
def wait(self):
|
if self.g.game.break_block(self.wart):
|
||||||
# wait for the item
|
self.break_deadline = time.monotonic() + self.BREAK_TIMEOUT
|
||||||
if self.wait_time > 0:
|
self.state = self.wait_break
|
||||||
self.wait_time -= utils.TICK
|
|
||||||
else:
|
else:
|
||||||
|
print('Break request skipped; blacklisting', self.wart)
|
||||||
|
self._blacklist_wart()
|
||||||
|
self.state = self.find_new_wart
|
||||||
|
|
||||||
|
def wait_break(self):
|
||||||
|
current = self.g.world.block_at(*self.wart)
|
||||||
|
if current != self.type_id:
|
||||||
|
print('Wart break confirmed at {}: {} -> {}'.format(
|
||||||
|
self.wart, self.type_id, current))
|
||||||
|
self.item_deadline = time.monotonic() + self.ITEM_TIMEOUT
|
||||||
|
self.item_wait_logged = False
|
||||||
self.state = self.select_wart
|
self.state = self.select_wart
|
||||||
|
elif time.monotonic() > self.break_deadline:
|
||||||
|
print('Wart did not break; blacklisting', self.wart)
|
||||||
|
self._blacklist_wart()
|
||||||
|
self.state = self.find_new_wart
|
||||||
|
|
||||||
def select_wart(self):
|
def select_wart(self):
|
||||||
p = utils.pint(self.g.pos)
|
|
||||||
|
|
||||||
if self.g.game.select_item([items.NETHERWART_ID]):
|
if self.g.game.select_item([items.NETHERWART_ID]):
|
||||||
self.state = self.wait_select
|
self.state = self.wait_select
|
||||||
self.wait_time = 0.5
|
return
|
||||||
else:
|
|
||||||
print('Aborting planting, no wart')
|
if time.monotonic() > self.item_deadline:
|
||||||
self.state = self.cleanup
|
print('No nether wart collected; skipping', self.wart)
|
||||||
|
self._blacklist_wart()
|
||||||
|
self.state = self.find_new_wart
|
||||||
|
elif not self.item_wait_logged:
|
||||||
|
print('Waiting for nether wart in inventory/hotbar')
|
||||||
|
self.item_wait_logged = True
|
||||||
|
|
||||||
def wait_select(self):
|
def wait_select(self):
|
||||||
# wait a bit to select
|
if self.g.get('holding') == items.NETHERWART_ID:
|
||||||
if self.wait_time > 0:
|
|
||||||
self.wait_time -= utils.TICK
|
|
||||||
else:
|
|
||||||
self.state = self.place_wart
|
self.state = self.place_wart
|
||||||
|
|
||||||
def place_wart(self):
|
def place_wart(self):
|
||||||
p = utils.pint(self.g.pos)
|
soul_sand = utils.padd(self.wart, path.BLOCK_BELOW)
|
||||||
self.g.game.place_block(p, BlockFace.TOP)
|
self.g.game.place_block(soul_sand, BlockFace.TOP)
|
||||||
print('Placed wart')
|
self.place_deadline = time.monotonic() + self.PLACE_TIMEOUT
|
||||||
self.state = self.wait_place
|
self.state = self.wait_place
|
||||||
self.wait_time = 0.5
|
|
||||||
|
|
||||||
def wait_place(self):
|
def wait_place(self):
|
||||||
# wait a bit for chunk data to update
|
current = self.g.world.block_at(*self.wart)
|
||||||
if self.wait_time > 0:
|
if current in blocks.NETHERWART_IDS and current != self.type_id:
|
||||||
self.wait_time -= utils.TICK
|
print('Nether wart replanted at', self.wart)
|
||||||
else:
|
|
||||||
self.state = self.cleanup
|
self.state = self.cleanup
|
||||||
|
elif time.monotonic() > self.place_deadline:
|
||||||
|
print('Nether wart did not replant; blacklisting', self.wart)
|
||||||
|
self._blacklist_wart()
|
||||||
|
self.state = self.find_new_wart
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self.g.look_at = None
|
self.g.look_at = None
|
||||||
self.state = self.done
|
self.state = self.done
|
||||||
|
|
||||||
def done(self):
|
def done(self):
|
||||||
# never gets ran, placeholder
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __init__(self, global_state):
|
def __init__(self, global_state):
|
||||||
self.g = global_state
|
self.g = global_state
|
||||||
self.state = self.idle
|
self.state = self.idle
|
||||||
|
|
||||||
self.wart = None
|
self.wart = None
|
||||||
|
self.type_id = None
|
||||||
self.bad_warts = []
|
self.bad_warts = []
|
||||||
self.wait_time = 0
|
self.break_deadline = None
|
||||||
|
self.item_deadline = None
|
||||||
|
self.place_deadline = None
|
||||||
|
self.item_wait_logged = False
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.state()
|
self.state()
|
||||||
|
|||||||
@@ -6,15 +6,20 @@ from mosfet.bot import FARMING_PITCH, pitch_lock_for_job, update_pitch
|
|||||||
|
|
||||||
|
|
||||||
class TestFarmingPitch(unittest.TestCase):
|
class TestFarmingPitch(unittest.TestCase):
|
||||||
def test_only_crop_farming_locks_pitch(self):
|
def test_crop_and_wart_farming_lock_pitch(self):
|
||||||
def farm_crop():
|
def farm_crop():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def farm_wart():
|
||||||
|
pass
|
||||||
|
|
||||||
def sleep():
|
def sleep():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
pitch_lock_for_job(Munch(state=farm_crop)), FARMING_PITCH)
|
pitch_lock_for_job(Munch(state=farm_crop)), FARMING_PITCH)
|
||||||
|
self.assertEqual(
|
||||||
|
pitch_lock_for_job(Munch(state=farm_wart)), FARMING_PITCH)
|
||||||
self.assertIsNone(pitch_lock_for_job(Munch(state=sleep)))
|
self.assertIsNone(pitch_lock_for_job(Munch(state=sleep)))
|
||||||
self.assertIsNone(pitch_lock_for_job(None))
|
self.assertIsNone(pitch_lock_for_job(None))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import unittest
|
||||||
|
from munch import Munch
|
||||||
|
|
||||||
|
from mosfet.commands import Commands
|
||||||
|
from mosfet.info import blocks, items
|
||||||
|
from mosfet.job import JobStates
|
||||||
|
from mosfet.jobs.gather_wart import GatherWartStates
|
||||||
|
|
||||||
|
|
||||||
|
class GatherWartTests(unittest.TestCase):
|
||||||
|
def test_nether_wart_states_are_indexed_for_patch_search(self):
|
||||||
|
self.assertTrue(blocks.NETHERWART_IDS.issubset(blocks.INDEXED_IDS))
|
||||||
|
|
||||||
|
def test_farm_wart_command_is_enabled_and_keeps_job_sequence(self):
|
||||||
|
old_wanted = items.WANTED_ITEMS
|
||||||
|
job = JobStates(Munch())
|
||||||
|
|
||||||
|
class FakeChat:
|
||||||
|
def __init__(self):
|
||||||
|
self.sent = []
|
||||||
|
|
||||||
|
def set_handler(self, _handler):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def send(self, message):
|
||||||
|
self.sent.append(message)
|
||||||
|
|
||||||
|
chat = FakeChat()
|
||||||
|
g = Munch(name='mosfet1', chat=chat, job=job,
|
||||||
|
inv={0: Munch(item_id=next(iter(items.BED_IDS)))})
|
||||||
|
try:
|
||||||
|
Commands(g).handle_chat(('CHAT', 'tester',
|
||||||
|
'tester: 1farm wart'))
|
||||||
|
self.assertEqual(chat.sent, ['ok'])
|
||||||
|
self.assertIs(job.state.__func__, job.farm_wart.__func__)
|
||||||
|
self.assertIs(job.farm_wart()[0], job.gather_wart_states)
|
||||||
|
self.assertIn(items.NETHERWART_ID, items.WANTED_ITEMS)
|
||||||
|
finally:
|
||||||
|
items.WANTED_ITEMS = old_wanted
|
||||||
|
|
||||||
|
def test_finds_only_indexed_mature_wart_allowed_by_work_signs(self):
|
||||||
|
position = (5, 64, 8)
|
||||||
|
mature = max(blocks.NETHERWART_IDS)
|
||||||
|
|
||||||
|
class FakeWorld:
|
||||||
|
def __init__(self):
|
||||||
|
self.search = None
|
||||||
|
self.filter_input = None
|
||||||
|
|
||||||
|
def find_blocks_indexed(self, center, block_ids, distance):
|
||||||
|
self.search = (center, block_ids, distance)
|
||||||
|
return [position]
|
||||||
|
|
||||||
|
def filter_work_positions(self, positions):
|
||||||
|
self.filter_input = list(positions)
|
||||||
|
return list(positions)
|
||||||
|
|
||||||
|
world = FakeWorld()
|
||||||
|
state = GatherWartStates(Munch(world=world, pos=(1.5, 64, 1.5)))
|
||||||
|
|
||||||
|
state.find_new_wart()
|
||||||
|
|
||||||
|
self.assertEqual(world.search, ((1, 64, 1), {mature}, 50))
|
||||||
|
self.assertEqual(world.filter_input, [position])
|
||||||
|
self.assertEqual(state.wart, position)
|
||||||
|
self.assertIs(state.state.__func__, state.nav_to_wart.__func__)
|
||||||
|
|
||||||
|
def test_harvests_confirms_replant_on_soul_sand_below_wart(self):
|
||||||
|
position = (5, 64, 8)
|
||||||
|
mature = max(blocks.NETHERWART_IDS)
|
||||||
|
unripe = min(blocks.NETHERWART_IDS)
|
||||||
|
|
||||||
|
class FakeWorld:
|
||||||
|
def __init__(self):
|
||||||
|
self.state_id = mature
|
||||||
|
|
||||||
|
def block_at(self, *_position):
|
||||||
|
return self.state_id
|
||||||
|
|
||||||
|
class FakeGame:
|
||||||
|
def __init__(self, global_state):
|
||||||
|
self.g = global_state
|
||||||
|
self.break_calls = []
|
||||||
|
self.place_calls = []
|
||||||
|
|
||||||
|
def break_block(self, target):
|
||||||
|
self.break_calls.append(target)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def select_item(self, item_ids):
|
||||||
|
self.g.holding = items.NETHERWART_ID
|
||||||
|
return items.NETHERWART_ID in item_ids
|
||||||
|
|
||||||
|
def place_block(self, target, face):
|
||||||
|
self.place_calls.append((target, face))
|
||||||
|
|
||||||
|
world = FakeWorld()
|
||||||
|
g = Munch(world=world, pos=position, holding=0, look_at=None)
|
||||||
|
game = FakeGame(g)
|
||||||
|
g.game = game
|
||||||
|
state = GatherWartStates(g)
|
||||||
|
state.wart = position
|
||||||
|
state.type_id = mature
|
||||||
|
|
||||||
|
state.break_wart()
|
||||||
|
self.assertIs(state.state.__func__, state.wait_break.__func__)
|
||||||
|
self.assertEqual(game.break_calls, [position])
|
||||||
|
|
||||||
|
world.state_id = blocks.AIR
|
||||||
|
state.wait_break()
|
||||||
|
self.assertIs(state.state.__func__, state.select_wart.__func__)
|
||||||
|
state.select_wart()
|
||||||
|
state.wait_select()
|
||||||
|
self.assertIs(state.state.__func__, state.place_wart.__func__)
|
||||||
|
|
||||||
|
state.place_wart()
|
||||||
|
support = (position[0], position[1] - 1, position[2])
|
||||||
|
self.assertEqual(game.place_calls, [(support, 1)])
|
||||||
|
world.state_id = unripe
|
||||||
|
state.wait_place()
|
||||||
|
self.assertIs(state.state.__func__, state.cleanup.__func__)
|
||||||
|
|
||||||
|
def test_unreachable_wart_is_blacklisted_without_undefined_variable(self):
|
||||||
|
position = (5, 64, 8)
|
||||||
|
|
||||||
|
class FakeWorld:
|
||||||
|
@staticmethod
|
||||||
|
def path_to_place(_start, _target):
|
||||||
|
return None
|
||||||
|
|
||||||
|
state = GatherWartStates(Munch(
|
||||||
|
world=FakeWorld(), pos=(1, 64, 1), path=[]))
|
||||||
|
state.wart = position
|
||||||
|
|
||||||
|
state.nav_to_wart()
|
||||||
|
|
||||||
|
self.assertIn(position, state.bad_warts)
|
||||||
|
self.assertIs(state.state.__func__, state.find_new_wart.__func__)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user