Add sign-labeled chest storage
This commit is contained in:
@@ -83,6 +83,25 @@ EMERALD_ID = get_id('emerald')
|
|||||||
BERRIES_ID = get_id('sweet_berries')
|
BERRIES_ID = get_id('sweet_berries')
|
||||||
IRON_INGOT_ID = get_id('iron_ingot')
|
IRON_INGOT_ID = get_id('iron_ingot')
|
||||||
|
|
||||||
|
|
||||||
|
_LABEL_ITEM_IDS = {
|
||||||
|
'seeds': get_set({
|
||||||
|
'beetroot', 'beetroot_seeds', 'carrot', 'melon_seeds',
|
||||||
|
'nether_wart', 'pitcher_pod', 'potato', 'pumpkin_seeds',
|
||||||
|
'torchflower_seeds', 'wheat_seeds',
|
||||||
|
}),
|
||||||
|
'wheat': {WHEAT_ID},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def storage_item_ids_for_label(label):
|
||||||
|
"""Return protocol item IDs assigned to a sign storage label."""
|
||||||
|
normalized = ' '.join(str(label).casefold().split()).strip(' :')
|
||||||
|
normalized = (normalized.rstrip('s')
|
||||||
|
if normalized not in ('seeds', 'beds') else normalized)
|
||||||
|
return set(_LABEL_ITEM_IDS.get(normalized, ()))
|
||||||
|
|
||||||
|
|
||||||
INIT_NEEDED_ITEMS = BED_IDS | FOOD_IDS
|
INIT_NEEDED_ITEMS = BED_IDS | FOOD_IDS
|
||||||
INIT_NEEDED_ITEMS.add(CHEST_ID)
|
INIT_NEEDED_ITEMS.add(CHEST_ID)
|
||||||
NEEDED_ITEMS = INIT_NEEDED_ITEMS
|
NEEDED_ITEMS = INIT_NEEDED_ITEMS
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ class CacheItemsStates:
|
|||||||
def init(self):
|
def init(self):
|
||||||
self.skip_slots = []
|
self.skip_slots = []
|
||||||
self.skip_items = []
|
self.skip_items = []
|
||||||
|
self.is_labeled_container = False
|
||||||
|
self.container_item_ids = None
|
||||||
|
|
||||||
num_stacks = self.g.game.count_inventory_slots()
|
num_stacks = self.g.game.count_inventory_slots()
|
||||||
minimum = getattr(
|
minimum = getattr(
|
||||||
@@ -36,25 +38,54 @@ class CacheItemsStates:
|
|||||||
self.state = self.cleanup
|
self.state = self.cleanup
|
||||||
|
|
||||||
def find_trapped_chests(self):
|
def find_trapped_chests(self):
|
||||||
print('Finding trapped chests...')
|
print('Finding labeled and trapped chests...')
|
||||||
w = self.g.world
|
w = self.g.world
|
||||||
p = utils.pint(self.g.pos)
|
p = utils.pint(self.g.pos)
|
||||||
|
|
||||||
|
self.labeled_chests = w.find_labeled_chests(p, 80)
|
||||||
|
self.checked_labeled_chests = set()
|
||||||
self.trapped_chests = w.find_blocks_indexed(p, blocks.TRAPPED_CHEST_IDS)
|
self.trapped_chests = w.find_blocks_indexed(p, blocks.TRAPPED_CHEST_IDS)
|
||||||
print('Found:', self.trapped_chests)
|
print('Found labeled chests:', len(self.labeled_chests),
|
||||||
|
'trapped chests:', self.trapped_chests)
|
||||||
self.state = self.choose_trapped_chest
|
self.state = self.choose_trapped_chest
|
||||||
|
|
||||||
def choose_trapped_chest(self):
|
def choose_trapped_chest(self):
|
||||||
print('Choosing a trapped chest...')
|
print('Choosing a labeled or trapped chest...')
|
||||||
w = self.g.world
|
w = self.g.world
|
||||||
p = utils.pint(self.g.pos)
|
p = utils.pint(self.g.pos)
|
||||||
c = self.g.chunks
|
|
||||||
|
for container in self.labeled_chests:
|
||||||
|
key = container['positions']
|
||||||
|
if key in self.checked_labeled_chests:
|
||||||
|
continue
|
||||||
|
self.checked_labeled_chests.add(key)
|
||||||
|
if not any(item.present and item.item_id in container['item_ids']
|
||||||
|
for item in self.g.inv.values()):
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.area = container['position']
|
||||||
|
self.container_item_ids = container['item_ids']
|
||||||
|
self.is_labeled_container = True
|
||||||
|
for opening in w.find_cache_openings(self.area):
|
||||||
|
navpath = w.path_to_place(p, opening)
|
||||||
|
if navpath:
|
||||||
|
print('Going to labeled chest', container['labels'],
|
||||||
|
self.area)
|
||||||
|
self.opening = opening
|
||||||
|
self.g.path = navpath
|
||||||
|
self.state = self.going_to_trapped_chest
|
||||||
|
return
|
||||||
|
print('No path to labeled chest', self.area)
|
||||||
|
|
||||||
if not len(self.trapped_chests):
|
if not len(self.trapped_chests):
|
||||||
print('No trapped chests')
|
self.is_labeled_container = False
|
||||||
|
self.container_item_ids = None
|
||||||
|
print('No labeled or trapped chests')
|
||||||
self.state = self.select_chest
|
self.state = self.select_chest
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.is_labeled_container = False
|
||||||
|
self.container_item_ids = None
|
||||||
chest = self.trapped_chests[0]
|
chest = self.trapped_chests[0]
|
||||||
self.area = chest
|
self.area = chest
|
||||||
|
|
||||||
@@ -83,6 +114,8 @@ class CacheItemsStates:
|
|||||||
self.state = self.open_chest
|
self.state = self.open_chest
|
||||||
|
|
||||||
def select_chest(self):
|
def select_chest(self):
|
||||||
|
self.is_labeled_container = False
|
||||||
|
self.container_item_ids = None
|
||||||
if self.g.game.select_item([items.CHEST_ID]):
|
if self.g.game.select_item([items.CHEST_ID]):
|
||||||
self.state = self.find_cache_spot
|
self.state = self.find_cache_spot
|
||||||
else:
|
else:
|
||||||
@@ -149,6 +182,41 @@ class CacheItemsStates:
|
|||||||
else:
|
else:
|
||||||
self.state = self.move_items
|
self.state = self.move_items
|
||||||
|
|
||||||
|
def _needed_inventory_slots_to_keep(self):
|
||||||
|
needed = set(items.NEEDED_ITEMS)
|
||||||
|
groups = (
|
||||||
|
items.BED_IDS, {items.CHEST_ID}, items.FOOD_IDS,
|
||||||
|
items.AXE_IDS, items.SHOVEL_IDS,
|
||||||
|
)
|
||||||
|
keep = set()
|
||||||
|
covered = set()
|
||||||
|
for group in groups:
|
||||||
|
group = needed.intersection(group)
|
||||||
|
if not group:
|
||||||
|
continue
|
||||||
|
covered.update(group)
|
||||||
|
stacks = [(slot, item) for slot, item in self.g.inv.items()
|
||||||
|
if 5 <= slot <= 45 and item.present and
|
||||||
|
item.item_id in group]
|
||||||
|
if stacks:
|
||||||
|
keep.add(max(stacks, key=lambda pair: pair[1].item_count)[0])
|
||||||
|
|
||||||
|
for item_id in needed - covered:
|
||||||
|
stacks = [(slot, item) for slot, item in self.g.inv.items()
|
||||||
|
if 5 <= slot <= 45 and item.present and
|
||||||
|
item.item_id == item_id]
|
||||||
|
if stacks:
|
||||||
|
keep.add(max(stacks, key=lambda pair: pair[1].item_count)[0])
|
||||||
|
return keep
|
||||||
|
|
||||||
|
def _window_player_slot(self, window_slot):
|
||||||
|
offset = window_slot - self.g.window.container_slots
|
||||||
|
if 0 <= offset < 27:
|
||||||
|
return 9 + offset
|
||||||
|
if 27 <= offset < 36:
|
||||||
|
return 36 + offset - 27
|
||||||
|
return None
|
||||||
|
|
||||||
def move_items(self):
|
def move_items(self):
|
||||||
if self.g.item_lock: return
|
if self.g.item_lock: return
|
||||||
w = self.g.window
|
w = self.g.window
|
||||||
@@ -177,10 +245,13 @@ class CacheItemsStates:
|
|||||||
print('Container is too full, aborting')
|
print('Container is too full, aborting')
|
||||||
self.g.game.close_window()
|
self.g.game.close_window()
|
||||||
self.g.look_at = None
|
self.g.look_at = None
|
||||||
self.state = self.cleanup
|
self.state = (self.close_chest if self.is_labeled_container
|
||||||
|
else self.cleanup)
|
||||||
return
|
return
|
||||||
|
|
||||||
slot_list = []
|
slot_list = []
|
||||||
|
needed_slots = (self._needed_inventory_slots_to_keep()
|
||||||
|
if self.is_labeled_container else set())
|
||||||
|
|
||||||
for slot_num in w_inventory_slots:
|
for slot_num in w_inventory_slots:
|
||||||
if slot_num not in w.contents:
|
if slot_num not in w.contents:
|
||||||
@@ -191,7 +262,14 @@ class CacheItemsStates:
|
|||||||
if not slot.present:
|
if not slot.present:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if slot.item_id in items.NEEDED_ITEMS:
|
if self.is_labeled_container:
|
||||||
|
if slot.item_id not in self.container_item_ids:
|
||||||
|
continue
|
||||||
|
inventory_slot = self._window_player_slot(slot_num)
|
||||||
|
if (slot.item_id in items.NEEDED_ITEMS and
|
||||||
|
inventory_slot in needed_slots):
|
||||||
|
continue
|
||||||
|
elif slot.item_id in items.NEEDED_ITEMS:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if slot_num in self.skip_slots:
|
if slot_num in self.skip_slots:
|
||||||
@@ -222,7 +300,12 @@ class CacheItemsStates:
|
|||||||
self.g.game.close_window()
|
self.g.game.close_window()
|
||||||
if not self.silent:
|
if not self.silent:
|
||||||
self.g.chat.send('cache at ' + str(self.area)[1:-1])
|
self.g.chat.send('cache at ' + str(self.area)[1:-1])
|
||||||
self.state = self.cleanup
|
if self.is_labeled_container:
|
||||||
|
self.is_labeled_container = False
|
||||||
|
self.container_item_ids = None
|
||||||
|
self.state = self.choose_trapped_chest
|
||||||
|
else:
|
||||||
|
self.state = self.cleanup
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self.g.look_at = None
|
self.g.look_at = None
|
||||||
@@ -244,6 +327,10 @@ class CacheItemsStates:
|
|||||||
self.skip_slots = []
|
self.skip_slots = []
|
||||||
self.skip_items = []
|
self.skip_items = []
|
||||||
|
|
||||||
|
self.labeled_chests = []
|
||||||
|
self.checked_labeled_chests = set()
|
||||||
|
self.is_labeled_container = False
|
||||||
|
self.container_item_ids = None
|
||||||
self.area = None
|
self.area = None
|
||||||
self.opening = None
|
self.opening = None
|
||||||
self.trapped_chests = []
|
self.trapped_chests = []
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class GrabSuppliesStates:
|
|||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
print('Started grab supplies states')
|
print('Started grab supplies states')
|
||||||
self.checked_barrels = []
|
self.checked_supply_chests = set()
|
||||||
|
|
||||||
used_slots = self.g.game.count_inventory_slots()
|
used_slots = self.g.game.count_inventory_slots()
|
||||||
print('used:', used_slots, 'total:', self.g.maximum_supply_slots)
|
print('used:', used_slots, 'total:', self.g.maximum_supply_slots)
|
||||||
@@ -41,6 +41,7 @@ class GrabSuppliesStates:
|
|||||||
|
|
||||||
self.checked_ground_items = set()
|
self.checked_ground_items = set()
|
||||||
self.checked_ground_groups = set()
|
self.checked_ground_groups = set()
|
||||||
|
self.checked_supply_chests = set()
|
||||||
self.ground_stack_counts = {}
|
self.ground_stack_counts = {}
|
||||||
self.ground_entity_id = None
|
self.ground_entity_id = None
|
||||||
self.ground_position = None
|
self.ground_position = None
|
||||||
@@ -78,8 +79,8 @@ class GrabSuppliesStates:
|
|||||||
self.ground_position = None
|
self.ground_position = None
|
||||||
|
|
||||||
def check_supplies(self):
|
def check_supplies(self):
|
||||||
# Search every configured item group on the ground before going to a
|
# Search every configured item group on the ground before looking in
|
||||||
# barrel; a missing bed must not hide a nearby chest or food drop.
|
# labeled chests; a missing bed must not hide a nearby chest or food drop.
|
||||||
missing = []
|
missing = []
|
||||||
for item_ids, limits in self.supplies.items():
|
for item_ids, limits in self.supplies.items():
|
||||||
minimum, maximum = limits
|
minimum, maximum = limits
|
||||||
@@ -110,9 +111,9 @@ class GrabSuppliesStates:
|
|||||||
|
|
||||||
if missing:
|
if missing:
|
||||||
self.target_items, self.target_minimum, self.target_maximum_stacks = missing[0]
|
self.target_items, self.target_minimum, self.target_maximum_stacks = missing[0]
|
||||||
print('No more ground drops; checking barrels for',
|
print('No more ground drops; checking supply-labeled chests for',
|
||||||
self.target_items)
|
self.target_items)
|
||||||
self.state = self.find_barrels
|
self.state = self.find_supply_chests
|
||||||
return
|
return
|
||||||
|
|
||||||
print('Aborting, dont need any supplies')
|
print('Aborting, dont need any supplies')
|
||||||
@@ -122,7 +123,11 @@ class GrabSuppliesStates:
|
|||||||
stack_count = self.ground_stack_counts.get(self.target_items, 0)
|
stack_count = self.ground_stack_counts.get(self.target_items, 0)
|
||||||
if self.target_maximum_stacks and stack_count >= self.target_maximum_stacks:
|
if self.target_maximum_stacks and stack_count >= self.target_maximum_stacks:
|
||||||
self.checked_ground_groups.add(self.target_items)
|
self.checked_ground_groups.add(self.target_items)
|
||||||
self.state = self.check_supplies
|
if (self.g.game.count_items(self.target_items) <
|
||||||
|
self.target_minimum):
|
||||||
|
self.state = self.find_supply_chests
|
||||||
|
else:
|
||||||
|
self.state = self.check_supplies
|
||||||
return
|
return
|
||||||
|
|
||||||
print('Looking for supplies on the ground...')
|
print('Looking for supplies on the ground...')
|
||||||
@@ -199,60 +204,52 @@ class GrabSuppliesStates:
|
|||||||
self.ground_position = None
|
self.ground_position = None
|
||||||
self.state = self.find_ground_items
|
self.state = self.find_ground_items
|
||||||
|
|
||||||
def find_barrels(self):
|
def find_supply_chests(self):
|
||||||
print('Finding barrels...')
|
print('Finding chests labeled for this bot\'s supplies...')
|
||||||
w = self.g.world
|
|
||||||
p = utils.pint(self.g.pos)
|
p = utils.pint(self.g.pos)
|
||||||
|
self.supply_chests = self.g.world.find_supply_chests(
|
||||||
|
p, self.g.name, 80)
|
||||||
|
print('Found:', len(self.supply_chests), 'supply-labeled chests')
|
||||||
|
self.state = self.choose_supply_chest
|
||||||
|
|
||||||
self.barrels = w.find_blocks_indexed(p, blocks.BARREL_IDS, 80)
|
def choose_supply_chest(self):
|
||||||
print('Found:', self.barrels)
|
print('Choosing a supply-labeled chest...')
|
||||||
self.state = self.choose_barrel
|
for container in self.supply_chests:
|
||||||
|
key = container['positions']
|
||||||
def choose_barrel(self):
|
if key in self.checked_supply_chests:
|
||||||
print('Choosing a barrel...')
|
|
||||||
for barrel in self.barrels:
|
|
||||||
if barrel in self.checked_barrels:
|
|
||||||
continue
|
continue
|
||||||
if barrel in self.bad_barrels:
|
self.checked_supply_chests.add(key)
|
||||||
continue
|
self.chest = container['position']
|
||||||
|
self.area = self.chest
|
||||||
print('Chose:', barrel)
|
print('Chose supply chest:', self.chest)
|
||||||
self.barrel = barrel
|
self.state = self.path_to_supply_chest
|
||||||
self.state = self.path_to_barrel
|
|
||||||
return
|
return
|
||||||
|
|
||||||
print('No barrels')
|
print('No more supply-labeled chests')
|
||||||
self.state = self.cleanup
|
self.state = self.cleanup
|
||||||
|
|
||||||
def path_to_barrel(self):
|
def path_to_supply_chest(self):
|
||||||
print('Finding path to barrel')
|
print('Finding path to supply chest')
|
||||||
w = self.g.world
|
|
||||||
p = utils.pint(self.g.pos)
|
p = utils.pint(self.g.pos)
|
||||||
c = self.g.chunks
|
navpath = self.g.world.path_to_place_faked(p, self.chest)
|
||||||
|
|
||||||
navpath = w.path_to_place_faked(p, self.barrel)
|
|
||||||
print('navpath:', navpath)
|
print('navpath:', navpath)
|
||||||
|
|
||||||
if navpath:
|
if navpath:
|
||||||
self.g.path = navpath[:-1]
|
self.g.path = navpath[:-1]
|
||||||
self.opening = self.g.path[-1]
|
self.opening = self.g.path[-1]
|
||||||
self.checked_barrels.append(self.barrel)
|
self.state = self.going_to_supply_chest
|
||||||
self.area = self.barrel
|
|
||||||
self.state = self.going_to_barrel
|
|
||||||
self.checked_supplies = []
|
self.checked_supplies = []
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
print('Barrel is unreachable; ending supply task')
|
print('Supply chest is unreachable; trying another labeled chest')
|
||||||
self.bad_barrels.append(self.barrel)
|
self.state = self.choose_supply_chest
|
||||||
self.state = self.cleanup
|
|
||||||
|
|
||||||
def going_to_barrel(self):
|
def going_to_supply_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.state = self.open_barrel
|
self.state = self.open_supply_chest
|
||||||
|
|
||||||
def open_barrel(self):
|
def open_supply_chest(self):
|
||||||
print('Opening barrel')
|
print('Opening supply chest')
|
||||||
self.g.game.open_container(self.area)
|
self.g.game.open_container(self.area)
|
||||||
self.wait_time = 1
|
self.wait_time = 1
|
||||||
self.state = self.wait
|
self.state = self.wait
|
||||||
@@ -288,8 +285,8 @@ class GrabSuppliesStates:
|
|||||||
self.state = self.grab_items
|
self.state = self.grab_items
|
||||||
return
|
return
|
||||||
|
|
||||||
print('Aborting, dont need any more supplies')
|
print('No matching items in this supply chest')
|
||||||
self.state = self.close_barrel
|
self.state = self.close_supply_chest
|
||||||
|
|
||||||
def grab_items(self):
|
def grab_items(self):
|
||||||
if self.g.item_lock: return
|
if self.g.item_lock: return
|
||||||
@@ -299,7 +296,7 @@ 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.get('container_slots', 0) != 27:
|
if w.get('container_slots', 0) not in (27, 54):
|
||||||
print('Got wrong container size, aborting')
|
print('Got wrong container size, aborting')
|
||||||
self.state = self.cleanup
|
self.state = self.cleanup
|
||||||
return
|
return
|
||||||
@@ -338,11 +335,11 @@ class GrabSuppliesStates:
|
|||||||
self.wait_time = 0.25
|
self.wait_time = 0.25
|
||||||
self.state = self.wait
|
self.state = self.wait
|
||||||
|
|
||||||
def close_barrel(self):
|
def close_supply_chest(self):
|
||||||
print('Closing barrel')
|
print('Closing supply chest')
|
||||||
self.g.game.close_window()
|
self.g.game.close_window()
|
||||||
self.g.look_at = None
|
self.g.look_at = None
|
||||||
self.state = self.choose_barrel
|
self.state = self.choose_supply_chest
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self.state = self.done
|
self.state = self.done
|
||||||
@@ -356,10 +353,9 @@ class GrabSuppliesStates:
|
|||||||
self.state = self.idle
|
self.state = self.idle
|
||||||
|
|
||||||
self.supplies = self.default_supplies()
|
self.supplies = self.default_supplies()
|
||||||
self.barrels = []
|
self.supply_chests = []
|
||||||
self.checked_barrels = []
|
self.checked_supply_chests = set()
|
||||||
self.bad_barrels = []
|
self.supply_chest = None
|
||||||
self.barrel = None
|
|
||||||
self.checked_supplies = []
|
self.checked_supplies = []
|
||||||
self.target_items = None
|
self.target_items = None
|
||||||
self.maximum_stacks = 0
|
self.maximum_stacks = 0
|
||||||
|
|||||||
+117
@@ -9,6 +9,7 @@ from copy import copy
|
|||||||
from mosfet import utils
|
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 mobs
|
from mosfet.info import mobs
|
||||||
|
|
||||||
class World:
|
class World:
|
||||||
@@ -76,6 +77,122 @@ class World:
|
|||||||
result.sort(key=lambda sign: utils.phyp(center, sign['position']))
|
result.sort(key=lambda sign: utils.phyp(center, sign['position']))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _double_chest_positions(self, chest):
|
||||||
|
state_id = self.block_at(*chest)
|
||||||
|
properties = blocks.PROPS.get(state_id, {})
|
||||||
|
chest_type = properties.get('type')
|
||||||
|
if chest_type not in ('left', 'right'):
|
||||||
|
return (tuple(chest),)
|
||||||
|
|
||||||
|
other_type = 'right' if chest_type == 'left' else 'left'
|
||||||
|
for offset in ((1, 0, 0), (-1, 0, 0), (0, 0, 1), (0, 0, -1)):
|
||||||
|
partner = utils.padd(chest, offset)
|
||||||
|
other = blocks.PROPS.get(self.block_at(*partner), {})
|
||||||
|
if (other.get('type') == other_type and
|
||||||
|
other.get('facing') == properties.get('facing')):
|
||||||
|
return tuple(sorted((tuple(chest), partner)))
|
||||||
|
return (tuple(chest),)
|
||||||
|
|
||||||
|
def find_labeled_chests(self, center, distance=80):
|
||||||
|
"""Find single/double chests assigned item categories by nearby signs."""
|
||||||
|
grouped = {}
|
||||||
|
sign_offsets = (
|
||||||
|
(0, 0, 0), (0, -1, 0),
|
||||||
|
(1, 0, 0), (-1, 0, 0), (0, 0, 1), (0, 0, -1),
|
||||||
|
)
|
||||||
|
for sign in self.find_signs(center, distance):
|
||||||
|
categories = set()
|
||||||
|
labels = set()
|
||||||
|
for line in sign['lines']:
|
||||||
|
item_ids = items.storage_item_ids_for_label(line)
|
||||||
|
if item_ids:
|
||||||
|
categories.update(item_ids)
|
||||||
|
labels.add(line.strip())
|
||||||
|
if not categories:
|
||||||
|
continue
|
||||||
|
|
||||||
|
sign_pos = sign['position']
|
||||||
|
adjacent = {
|
||||||
|
utils.padd(sign_pos, offset)
|
||||||
|
for offset in sign_offsets
|
||||||
|
if self.block_at(*utils.padd(sign_pos, offset)) in blocks.CHEST_IDS
|
||||||
|
}
|
||||||
|
if not adjacent:
|
||||||
|
nearby = self.find_blocks_indexed(
|
||||||
|
sign_pos, blocks.CHEST_IDS, 2)
|
||||||
|
if nearby:
|
||||||
|
nearest = min(utils.phyp(sign_pos, chest)
|
||||||
|
for chest in nearby)
|
||||||
|
adjacent = {chest for chest in nearby
|
||||||
|
if utils.phyp(sign_pos, chest) == nearest}
|
||||||
|
|
||||||
|
for chest in adjacent:
|
||||||
|
positions = self._double_chest_positions(chest)
|
||||||
|
record = grouped.setdefault(positions, {
|
||||||
|
'position': tuple(chest),
|
||||||
|
'positions': positions,
|
||||||
|
'item_ids': set(),
|
||||||
|
'labels': set(),
|
||||||
|
'sign_positions': set(),
|
||||||
|
})
|
||||||
|
record['item_ids'].update(categories)
|
||||||
|
record['labels'].update(labels)
|
||||||
|
record['sign_positions'].add(sign_pos)
|
||||||
|
|
||||||
|
result = list(grouped.values())
|
||||||
|
result.sort(key=lambda chest: utils.phyp(center, chest['position']))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def find_supply_chests(self, center, bot_name, distance=80):
|
||||||
|
"""Find chests whose sign is addressed to this bot's supplies."""
|
||||||
|
bot_name = str(bot_name).casefold()
|
||||||
|
group_name = (bot_name[:-1] if len(bot_name) > 1 and
|
||||||
|
bot_name[-1] in '123' else None)
|
||||||
|
addresses = {bot_name}
|
||||||
|
if group_name:
|
||||||
|
addresses.add(group_name)
|
||||||
|
|
||||||
|
grouped = {}
|
||||||
|
sign_offsets = (
|
||||||
|
(0, 0, 0), (0, -1, 0),
|
||||||
|
(1, 0, 0), (-1, 0, 0), (0, 0, 1), (0, 0, -1),
|
||||||
|
)
|
||||||
|
for sign in self.find_signs(center, distance):
|
||||||
|
text = ' '.join(line for line in sign['lines'] if line.strip())
|
||||||
|
tokens = re.findall(r'[a-z0-9_]+', text.casefold())
|
||||||
|
if (len(tokens) < 2 or tokens[0] not in addresses or
|
||||||
|
'supplies' not in tokens[1:]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
sign_pos = sign['position']
|
||||||
|
candidates = {
|
||||||
|
utils.padd(sign_pos, offset)
|
||||||
|
for offset in sign_offsets
|
||||||
|
if self.block_at(*utils.padd(sign_pos, offset)) in blocks.CHEST_IDS
|
||||||
|
}
|
||||||
|
if not candidates:
|
||||||
|
nearby = self.find_blocks_indexed(
|
||||||
|
sign_pos, blocks.CHEST_IDS, 2)
|
||||||
|
if nearby:
|
||||||
|
nearest = min(utils.phyp(sign_pos, chest) for chest in nearby)
|
||||||
|
candidates = {chest for chest in nearby
|
||||||
|
if utils.phyp(sign_pos, chest) == nearest}
|
||||||
|
|
||||||
|
for chest in candidates:
|
||||||
|
positions = self._double_chest_positions(chest)
|
||||||
|
record = grouped.setdefault(positions, {
|
||||||
|
'position': tuple(chest),
|
||||||
|
'positions': positions,
|
||||||
|
'labels': set(),
|
||||||
|
'sign_positions': set(),
|
||||||
|
})
|
||||||
|
record['labels'].update(tokens[1:])
|
||||||
|
record['sign_positions'].add(sign_pos)
|
||||||
|
|
||||||
|
result = list(grouped.values())
|
||||||
|
result.sort(key=lambda chest: utils.phyp(center, chest['position']))
|
||||||
|
return result
|
||||||
|
|
||||||
def sign_policies(self, bot_name, distance=64):
|
def sign_policies(self, bot_name, distance=64):
|
||||||
"""Parse sign policies addressed to this bot or its numbered group."""
|
"""Parse sign policies addressed to this bot or its numbered group."""
|
||||||
bot_name = str(bot_name).casefold()
|
bot_name = str(bot_name).casefold()
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class GatherWoodPortTests(unittest.TestCase):
|
|||||||
self.assertTrue(items.AXE_IDS.issubset(items.NEEDED_ITEMS))
|
self.assertTrue(items.AXE_IDS.issubset(items.NEEDED_ITEMS))
|
||||||
self.assertTrue(items.SAPLING_IDS.issubset(items.WANTED_ITEMS))
|
self.assertTrue(items.SAPLING_IDS.issubset(items.WANTED_ITEMS))
|
||||||
|
|
||||||
job.grab_supplies_states.state = job.grab_supplies_states.find_barrels
|
job.grab_supplies_states.state = job.grab_supplies_states.find_supply_chests
|
||||||
job.clear_leaves_states.state = job.clear_leaves_states.find_leaves
|
job.clear_leaves_states.state = job.clear_leaves_states.find_leaves
|
||||||
job.plant_tree_states.state = job.plant_tree_states.check_feet
|
job.plant_tree_states.state = job.plant_tree_states.check_feet
|
||||||
job.grab_sapling_states.state = job.grab_sapling_states.find_saplings
|
job.grab_sapling_states.state = job.grab_sapling_states.find_saplings
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ from mosfet.jobs.grab_supplies import GrabSuppliesStates
|
|||||||
class GroundSupplyTests(unittest.TestCase):
|
class GroundSupplyTests(unittest.TestCase):
|
||||||
def test_default_needed_supplies_are_configured_by_constructor(self):
|
def test_default_needed_supplies_are_configured_by_constructor(self):
|
||||||
state = GrabSuppliesStates(Munch())
|
state = GrabSuppliesStates(Munch())
|
||||||
|
self.assertEqual(items.FOOD, [
|
||||||
|
'cooked_porkchop', 'cooked_beef', 'bread', 'cooked_chicken',
|
||||||
|
'cooked_cod', 'cooked_salmon',
|
||||||
|
])
|
||||||
|
self.assertEqual(items.FOOD_IDS, items.get_set({
|
||||||
|
'cooked_porkchop', 'cooked_beef', 'bread', 'cooked_chicken',
|
||||||
|
'cooked_cod', 'cooked_salmon',
|
||||||
|
}))
|
||||||
self.assertEqual(state.supplies, {
|
self.assertEqual(state.supplies, {
|
||||||
tuple(items.BED_IDS): (1, 1),
|
tuple(items.BED_IDS): (1, 1),
|
||||||
(items.CHEST_ID,): (1, 1),
|
(items.CHEST_ID,): (1, 1),
|
||||||
@@ -44,19 +52,50 @@ class GroundSupplyTests(unittest.TestCase):
|
|||||||
self.assertEqual(state.target_items, (items.CHEST_ID,))
|
self.assertEqual(state.target_items, (items.CHEST_ID,))
|
||||||
self.assertIs(state.state.__func__, state.find_ground_items.__func__)
|
self.assertIs(state.state.__func__, state.find_ground_items.__func__)
|
||||||
|
|
||||||
def test_unreachable_barrel_ends_supply_task_without_trying_more(self):
|
def test_supply_selection_uses_only_addressed_supply_chests(self):
|
||||||
|
position = (4, 64, 0)
|
||||||
|
chest_record = {
|
||||||
|
'position': position,
|
||||||
|
'positions': (position,),
|
||||||
|
'labels': {'mosfet supplies'},
|
||||||
|
'sign_positions': {(4, 64, 1)},
|
||||||
|
}
|
||||||
|
|
||||||
class FakeWorld:
|
class FakeWorld:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def path_to_place_faked(_start, _barrel):
|
def find_supply_chests(_center, _bot_name, _distance):
|
||||||
return None
|
return [chest_record]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def find_blocks_indexed(*_args):
|
||||||
|
raise AssertionError('grab_supplies must not search barrels')
|
||||||
|
|
||||||
|
state = GrabSuppliesStates(Munch(
|
||||||
|
name='mosfet1', pos=(0.5, 64.0, 0.5), world=FakeWorld()))
|
||||||
|
state.find_supply_chests()
|
||||||
|
state.choose_supply_chest()
|
||||||
|
|
||||||
|
self.assertEqual(state.chest, position)
|
||||||
|
self.assertIs(state.state.__func__, state.path_to_supply_chest.__func__)
|
||||||
|
|
||||||
|
def test_unreachable_supply_chest_ends_task_without_searching_barrels(self):
|
||||||
|
class FakeWorld:
|
||||||
|
@staticmethod
|
||||||
|
def path_to_place_faked(_start, _chest):
|
||||||
|
return None
|
||||||
|
|
||||||
|
position = (10, 64, 10)
|
||||||
state = GrabSuppliesStates(Munch(
|
state = GrabSuppliesStates(Munch(
|
||||||
pos=(0.5, 64.0, 0.5), world=FakeWorld(), chunks=Munch()))
|
pos=(0.5, 64.0, 0.5), world=FakeWorld(), chunks=Munch()))
|
||||||
state.barrel = (10, 64, 10)
|
state.supply_chests = [{
|
||||||
state.path_to_barrel()
|
'position': position, 'positions': (position,),
|
||||||
|
'labels': {'mosfet supplies'},
|
||||||
|
}]
|
||||||
|
state.choose_supply_chest()
|
||||||
|
state.path_to_supply_chest()
|
||||||
|
state.choose_supply_chest()
|
||||||
|
|
||||||
self.assertIs(state.state.__func__, state.cleanup.__func__)
|
self.assertIs(state.state.__func__, state.cleanup.__func__)
|
||||||
self.assertEqual(state.bad_barrels, [(10, 64, 10)])
|
|
||||||
|
|
||||||
def test_job_walks_to_and_picks_up_dropped_supply_before_barrels(self):
|
def test_job_walks_to_and_picks_up_dropped_supply_before_barrels(self):
|
||||||
axe_id = next(iter(items.AXE_IDS))
|
axe_id = next(iter(items.AXE_IDS))
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import unittest
|
||||||
|
from munch import Munch
|
||||||
|
|
||||||
|
from mosfet.info import blocks, items
|
||||||
|
from mosfet.jobs.cache_items import CacheItemsStates
|
||||||
|
from mosfet.world import World
|
||||||
|
|
||||||
|
|
||||||
|
class LabeledStorageTests(unittest.TestCase):
|
||||||
|
def test_labels_resolve_to_item_categories(self):
|
||||||
|
seeds = items.storage_item_ids_for_label('Seeds')
|
||||||
|
wheat = items.storage_item_ids_for_label('Wheat')
|
||||||
|
|
||||||
|
self.assertEqual(seeds, items.get_set({
|
||||||
|
'beetroot', 'beetroot_seeds', 'carrot', 'melon_seeds',
|
||||||
|
'nether_wart', 'pitcher_pod', 'potato', 'pumpkin_seeds',
|
||||||
|
'torchflower_seeds', 'wheat_seeds',
|
||||||
|
}))
|
||||||
|
self.assertEqual(items.storage_item_ids_for_label('Dirt'), set())
|
||||||
|
self.assertEqual(items.storage_item_ids_for_label('Stone'), set())
|
||||||
|
self.assertIn(items.WHEAT_SEEDS_ID, seeds)
|
||||||
|
self.assertEqual(wheat, {items.WHEAT_ID})
|
||||||
|
self.assertNotIn(items.WHEAT_SEEDS_ID, wheat)
|
||||||
|
|
||||||
|
def test_sign_label_associates_with_adjacent_double_chest(self):
|
||||||
|
sign_pos = (0, 64, 0)
|
||||||
|
left_chest = (0, 64, 1)
|
||||||
|
right_chest = (1, 64, 1)
|
||||||
|
blocks_by_pos = {left_chest: 5552, right_chest: 5560}
|
||||||
|
chunks = Munch(
|
||||||
|
block_entities={sign_pos: {
|
||||||
|
'type': 7,
|
||||||
|
'data': {'front_text': {'messages': [
|
||||||
|
'Seeds', '', '', '',
|
||||||
|
]}},
|
||||||
|
}},
|
||||||
|
index={},
|
||||||
|
get_block_at=lambda *pos: blocks_by_pos.get(tuple(pos)),
|
||||||
|
)
|
||||||
|
chests = World(Munch(chunks=chunks)).find_labeled_chests(
|
||||||
|
sign_pos, 10)
|
||||||
|
|
||||||
|
self.assertEqual(len(chests), 1)
|
||||||
|
self.assertEqual(chests[0]['positions'], (left_chest, right_chest))
|
||||||
|
self.assertIn(items.WHEAT_SEEDS_ID, chests[0]['item_ids'])
|
||||||
|
self.assertEqual(chests[0]['labels'], {'Seeds'})
|
||||||
|
|
||||||
|
def test_wheat_chest_does_not_cache_seeds(self):
|
||||||
|
wheat = Munch(present=True, item_id=items.WHEAT_ID, item_count=64)
|
||||||
|
seeds = Munch(present=True, item_id=items.WHEAT_SEEDS_ID, item_count=64)
|
||||||
|
clicks = []
|
||||||
|
|
||||||
|
class FakeGame:
|
||||||
|
@staticmethod
|
||||||
|
def count_window_slots():
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def click_window(*args):
|
||||||
|
clicks.append(args)
|
||||||
|
|
||||||
|
cache = CacheItemsStates(Munch(
|
||||||
|
inv={9: wheat, 10: seeds}, item_lock=False,
|
||||||
|
window=Munch(container_slots=27, contents={
|
||||||
|
**dict.fromkeys(range(27)), 27: wheat, 28: seeds,
|
||||||
|
}),
|
||||||
|
game=FakeGame(),
|
||||||
|
))
|
||||||
|
cache.is_labeled_container = True
|
||||||
|
cache.container_item_ids = items.storage_item_ids_for_label('Wheat')
|
||||||
|
cache.move_items()
|
||||||
|
|
||||||
|
self.assertEqual(clicks, [(27, 0, 1, wheat)])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user