From c79ee932a3562c7dc0c63e7e7f797248fe28dbb9 Mon Sep 17 00:00:00 2001 From: Tanner Date: Sun, 8 Nov 2026 17:08:12 +0000 Subject: [PATCH] Cache after sleep near labeled chests --- mosfet/jobs/cache_items.py | 30 ++++++++- mosfet/jobs/gather_crop.py | 12 ++-- mosfet/jobs/sleep_with_bed.py | 3 + tests/test_cache_items.py | 106 ++++++++++++++++++++++++++++++++ tests/test_sign_work_targets.py | 4 +- tests/test_sleep_with_bed.py | 21 +++++++ 6 files changed, 166 insertions(+), 10 deletions(-) diff --git a/mosfet/jobs/cache_items.py b/mosfet/jobs/cache_items.py index 33a8589..1f6e9d7 100644 --- a/mosfet/jobs/cache_items.py +++ b/mosfet/jobs/cache_items.py @@ -3,7 +3,7 @@ import time import importlib import random from itertools import count -from math import floor +from math import ceil, floor from minecraft.networking.types import BlockFace @@ -19,6 +19,27 @@ class CacheItemsStates: def idle(self): return None + def _has_compatible_labeled_chest_near(self, bed, distance=16): + world = self.g.get('world') + if world is None: + return False + keep_slots = self._needed_inventory_slots_to_keep() + for chest in world.find_labeled_chests(bed, distance): + positions = chest.get('positions') or (chest['position'],) + if min(utils.phyp(bed, position) for position in positions) > distance: + continue + item_ids = chest['item_ids'] + for slot, item in self.g.inv.items(): + if (not 9 <= slot <= 44 or not item.present or + item.item_id not in item_ids or + item.item_id in items.WANTED_ITEMS): + continue + if (item.item_id in items.NEEDED_ITEMS and + slot in keep_slots): + continue + return True + return False + def init(self): self.skip_inventory_slots = set() self.skip_items = [] @@ -30,8 +51,15 @@ class CacheItemsStates: self.container_item_ids = None num_stacks = self.g.game.count_inventory_slots() + has_override = 'minimum' in self.__dict__ minimum = self.__dict__.pop( 'minimum', self.g.get('minimum_cache_slots', 27)) + post_sleep_bed = self.g.pop('post_sleep_cache_bed', None) + if (not has_override and post_sleep_bed is not None and + self._has_compatible_labeled_chest_near(post_sleep_bed)): + minimum = ceil(minimum / 2) + print('Lowering cache threshold after sleep near a compatible chest:', + minimum) print('Inventory amount:', num_stacks) if num_stacks == 0: self.state = self.cleanup diff --git a/mosfet/jobs/gather_crop.py b/mosfet/jobs/gather_crop.py index 7d5879d..43d651c 100644 --- a/mosfet/jobs/gather_crop.py +++ b/mosfet/jobs/gather_crop.py @@ -105,9 +105,10 @@ class GatherCropStates: 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 + print('No good crops left; running maintenance after {} harvests'.format( + self.completed_crops)) + self.completed_crops = 0 + self.state = self.cleanup return print('Found crop:', crop, @@ -116,10 +117,6 @@ class GatherCropStates: 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) @@ -251,7 +248,6 @@ class GatherCropStates: 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 diff --git a/mosfet/jobs/sleep_with_bed.py b/mosfet/jobs/sleep_with_bed.py index 0f6a164..f692ceb 100644 --- a/mosfet/jobs/sleep_with_bed.py +++ b/mosfet/jobs/sleep_with_bed.py @@ -21,6 +21,7 @@ class SleepWithBedStates: return None def init(self): + self.sleep_confirmed = False self.my_bed = False self.area = None self.opening = None @@ -371,6 +372,8 @@ class SleepWithBedStates: return self.g.look_at = None self.g.path = [] + if self.sleep_confirmed and self.area is not None: + self.g.post_sleep_cache_bed = tuple(self.area) self.my_bed = False self.force_new_bed = False self.state = self.done diff --git a/tests/test_cache_items.py b/tests/test_cache_items.py index 17a7b73..80737fd 100644 --- a/tests/test_cache_items.py +++ b/tests/test_cache_items.py @@ -46,6 +46,112 @@ class CacheItemsTests(unittest.TestCase): finally: items.WANTED_ITEMS = old_wanted + def test_post_sleep_halves_threshold_only_with_compatible_nearby_chest(self): + bed = (0, 64, 0) + chest_position = (5, 64, 0) + item_id = items.get_id('stone') + + class FakeGame: + @staticmethod + def count_inventory_slots(): + return 14 + + class NearbyWorld: + @staticmethod + def find_labeled_chests(_center, _distance): + return [{ + 'position': chest_position, + 'positions': (chest_position,), + 'item_ids': {item_id}, + }] + + g = Munch( + game=FakeGame(), world=NearbyWorld(), + minimum_cache_slots=27, post_sleep_cache_bed=bed, + inv={9: Munch(present=True, item_id=item_id, item_count=64)}) + state = CacheItemsStates(g) + + state.init() + + self.assertIs(state.state.__func__, state.find_trapped_chests.__func__) + self.assertNotIn('post_sleep_cache_bed', g) + + def test_post_sleep_threshold_rounds_27_up_to_14(self): + bed = (0, 64, 0) + item_id = items.get_id('stone') + + class FakeGame: + @staticmethod + def count_inventory_slots(): + return 13 + + class NearbyWorld: + @staticmethod + def find_labeled_chests(_center, _distance): + return [{ + 'position': (5, 64, 0), + 'positions': ((5, 64, 0),), + 'item_ids': {item_id}, + }] + + g = Munch( + game=FakeGame(), world=NearbyWorld(), minimum_cache_slots=27, + post_sleep_cache_bed=bed, + inv={9: Munch(present=True, item_id=item_id, item_count=64)}) + state = CacheItemsStates(g) + + state.init() + + self.assertIs(state.state.__func__, state.cleanup.__func__) + + def test_post_sleep_does_not_lower_threshold_without_compatible_nearby_chest(self): + class FakeGame: + @staticmethod + def count_inventory_slots(): + return 14 + + class EmptyWorld: + @staticmethod + def find_labeled_chests(_center, _distance): + return [] + + g = Munch( + game=FakeGame(), world=EmptyWorld(), minimum_cache_slots=27, + post_sleep_cache_bed=(0, 64, 0), inv={}) + state = CacheItemsStates(g) + + state.init() + + self.assertIs(state.state.__func__, state.cleanup.__func__) + self.assertNotIn('post_sleep_cache_bed', g) + + def test_post_sleep_ignores_compatible_chest_beyond_16_blocks(self): + item_id = items.get_id('stone') + + class FakeGame: + @staticmethod + def count_inventory_slots(): + return 14 + + class FarWorld: + @staticmethod + def find_labeled_chests(_center, _distance): + position = (17, 64, 0) + return [{ + 'position': position, + 'positions': (position,), + 'item_ids': {item_id}, + }] + + state = CacheItemsStates(Munch( + game=FakeGame(), world=FarWorld(), minimum_cache_slots=27, + post_sleep_cache_bed=(0, 64, 0), + inv={9: Munch(present=True, item_id=item_id, item_count=64)})) + + state.init() + + self.assertIs(state.state.__func__, state.cleanup.__func__) + def test_manual_cache_threshold_override_is_consumed_once(self): class FakeGame: @staticmethod diff --git a/tests/test_sign_work_targets.py b/tests/test_sign_work_targets.py index 926e833..0a8a5d3 100644 --- a/tests/test_sign_work_targets.py +++ b/tests/test_sign_work_targets.py @@ -29,9 +29,11 @@ class SignWorkTargetTests(unittest.TestCase): return crop_state state = GatherCropStates(Munch(world=FakeWorld(), pos=(0, 64, 0))) + state.completed_crops = 7 state.find_new_crop() - self.assertIs(state.state.__func__, state.wait_for_crops.__func__) + self.assertIs(state.state.__func__, state.cleanup.__func__) + self.assertEqual(state.completed_crops, 0) self.assertEqual(state.bad_crops, []) def test_tree_search_skips_out_of_bounds_tree_without_blacklisting_it(self): diff --git a/tests/test_sleep_with_bed.py b/tests/test_sleep_with_bed.py index df0a080..9c60828 100644 --- a/tests/test_sleep_with_bed.py +++ b/tests/test_sleep_with_bed.py @@ -52,6 +52,27 @@ class FakeGame: class TestSleepWithBed(unittest.TestCase): + def test_confirmed_sleep_records_bed_for_following_cache_pass(self): + bed = (1, 64, 2) + g = Munch(world=FakeWorld(), path=[], look_at=None) + state = SleepWithBedStates(g) + state.area = bed + state.sleep_confirmed = True + + state.cleanup() + + self.assertEqual(g.post_sleep_cache_bed, bed) + + def test_unconfirmed_sleep_does_not_trigger_post_sleep_cache(self): + g = Munch(world=FakeWorld(), path=[], look_at=None) + state = SleepWithBedStates(g) + state.area = (1, 64, 2) + state.sleep_confirmed = False + + state.cleanup() + + self.assertNotIn('post_sleep_cache_bed', g) + def test_bed_occupancy_only_logs_first_observation_and_changes(self): bed_states = [ state for state in blocks.BED_IDS