Grab useful supplies off the ground
This commit is contained in:
+13
-8
@@ -152,9 +152,9 @@ class LegacyJobStates:
|
|||||||
]
|
]
|
||||||
self.sleep_with_bed_states.silent = True
|
self.sleep_with_bed_states.silent = True
|
||||||
self.cache_items_states.silent = True
|
self.cache_items_states.silent = True
|
||||||
self.grab_supplies_states.supplies = {
|
self.grab_supplies_states.supplies = (
|
||||||
tuple(items.AXE_IDS): (1, 9),
|
self.grab_supplies_states.default_supplies())
|
||||||
}
|
self.grab_supplies_states.supplies[tuple(items.AXE_IDS)] = (1, 9)
|
||||||
|
|
||||||
items.set_needed(items.AXE_IDS)
|
items.set_needed(items.AXE_IDS)
|
||||||
items.set_wanted(items.SAPLING_IDS)
|
items.set_wanted(items.SAPLING_IDS)
|
||||||
@@ -286,9 +286,9 @@ class JobStates(LegacyJobStates):
|
|||||||
def farm_wood(self):
|
def farm_wood(self):
|
||||||
self.sleep_with_bed_states.silent = True
|
self.sleep_with_bed_states.silent = True
|
||||||
self.cache_items_states.silent = True
|
self.cache_items_states.silent = True
|
||||||
self.grab_supplies_states.supplies = {
|
self.grab_supplies_states.supplies = (
|
||||||
tuple(items.AXE_IDS): (1, 9),
|
self.grab_supplies_states.default_supplies())
|
||||||
}
|
self.grab_supplies_states.supplies[tuple(items.AXE_IDS)] = (1, 9)
|
||||||
items.set_needed(items.AXE_IDS)
|
items.set_needed(items.AXE_IDS)
|
||||||
items.set_wanted(items.SAPLING_IDS)
|
items.set_wanted(items.SAPLING_IDS)
|
||||||
return [
|
return [
|
||||||
@@ -312,8 +312,13 @@ class JobStates(LegacyJobStates):
|
|||||||
items.WHEAT_SEEDS_ID,
|
items.WHEAT_SEEDS_ID,
|
||||||
items.BEETROOT_SEEDS_ID,
|
items.BEETROOT_SEEDS_ID,
|
||||||
})
|
})
|
||||||
return [self.gather_crop_states, self.sleep_with_bed_states,
|
return [
|
||||||
self.eat_food_states, self.cache_items_states]
|
self.grab_supplies_states,
|
||||||
|
self.gather_crop_states,
|
||||||
|
self.sleep_with_bed_states,
|
||||||
|
self.eat_food_states,
|
||||||
|
self.cache_items_states
|
||||||
|
]
|
||||||
|
|
||||||
def eat(self):
|
def eat(self):
|
||||||
return [self.eat_food_states]
|
return [self.eat_food_states]
|
||||||
|
|||||||
@@ -16,6 +16,15 @@ from mosfet.info import items
|
|||||||
from mosfet.info import mobs
|
from mosfet.info import mobs
|
||||||
|
|
||||||
class GrabSuppliesStates:
|
class GrabSuppliesStates:
|
||||||
|
DEFAULT_SUPPLIES = {
|
||||||
|
tuple(items.BED_IDS): (1, 1),
|
||||||
|
(items.CHEST_ID,): (1, 1),
|
||||||
|
tuple(items.FOOD_IDS): (1, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
def default_supplies(self):
|
||||||
|
return dict(self.DEFAULT_SUPPLIES)
|
||||||
|
|
||||||
def idle(self):
|
def idle(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -30,31 +39,166 @@ class GrabSuppliesStates:
|
|||||||
self.state = self.cleanup
|
self.state = self.cleanup
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.checked_ground_items = set()
|
||||||
|
self.checked_ground_groups = set()
|
||||||
|
self.ground_stack_counts = {}
|
||||||
|
self.ground_entity_id = None
|
||||||
|
self.ground_position = None
|
||||||
if self.supplies:
|
if self.supplies:
|
||||||
self.state = self.check_supplies
|
self.state = self.check_supplies
|
||||||
else:
|
else:
|
||||||
print('Aborting getting supplies, none specified')
|
print('Aborting getting supplies, none specified')
|
||||||
self.state = self.cleanup
|
self.state = self.cleanup
|
||||||
|
|
||||||
|
def _has_nearby_ground_items(self, item_ids, maximum_stacks):
|
||||||
|
if (maximum_stacks and
|
||||||
|
self.ground_stack_counts.get(item_ids, 0) >= maximum_stacks):
|
||||||
|
return False
|
||||||
|
p = utils.pint(self.g.pos)
|
||||||
|
return any(
|
||||||
|
obj.entity_id not in self.checked_ground_items and
|
||||||
|
utils.phyp(p, (obj.x, obj.y, obj.z)) <= 16
|
||||||
|
for obj in self.g.world.find_objects(item_ids))
|
||||||
|
|
||||||
|
def has_nearby_ground_supply(self):
|
||||||
|
return any(
|
||||||
|
self._has_nearby_ground_items(item_ids, limits[1])
|
||||||
|
for item_ids, limits in self.supplies.items())
|
||||||
|
|
||||||
|
def _record_ground_pickup(self):
|
||||||
|
self.checked_ground_items.add(self.ground_entity_id)
|
||||||
|
self.ground_stack_counts[self.target_items] = (
|
||||||
|
self.ground_stack_counts.get(self.target_items, 0) + 1)
|
||||||
|
if (self.target_maximum_stacks and
|
||||||
|
self.ground_stack_counts[self.target_items] >=
|
||||||
|
self.target_maximum_stacks):
|
||||||
|
self.checked_ground_groups.add(self.target_items)
|
||||||
|
self.g.path = []
|
||||||
|
self.ground_entity_id = None
|
||||||
|
self.ground_position = None
|
||||||
|
|
||||||
def check_supplies(self):
|
def check_supplies(self):
|
||||||
# check if we need to grab anything
|
# Search every configured item group on the ground before going to a
|
||||||
for items, limits in self.supplies.items():
|
# barrel; a missing bed must not hide a nearby chest or food drop.
|
||||||
|
missing = []
|
||||||
|
for item_ids, limits in self.supplies.items():
|
||||||
minimum, maximum = limits
|
minimum, maximum = limits
|
||||||
print('Checking items:', items)
|
print('Checking items:', item_ids)
|
||||||
num_items = self.g.game.count_items(items)
|
num_items = self.g.game.count_items(item_ids)
|
||||||
print('Have:', num_items)
|
print('Have:', num_items)
|
||||||
|
|
||||||
|
if (item_ids not in self.checked_ground_groups and
|
||||||
|
self._has_nearby_ground_items(item_ids, maximum)):
|
||||||
|
print('Found matching supplies on the ground')
|
||||||
|
self.target_items = item_ids
|
||||||
|
self.target_minimum = minimum
|
||||||
|
self.target_maximum_stacks = maximum
|
||||||
|
self.state = self.find_ground_items
|
||||||
|
return
|
||||||
|
|
||||||
if num_items >= minimum:
|
if num_items >= minimum:
|
||||||
print('Have enough, skipping')
|
print('Have enough, skipping')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print('Need at least one item')
|
missing.append((item_ids, minimum, maximum))
|
||||||
|
if item_ids not in self.checked_ground_groups:
|
||||||
|
self.target_items = item_ids
|
||||||
|
self.target_minimum = minimum
|
||||||
|
self.target_maximum_stacks = maximum
|
||||||
|
self.state = self.find_ground_items
|
||||||
|
return
|
||||||
|
|
||||||
|
if missing:
|
||||||
|
self.target_items, self.target_minimum, self.target_maximum_stacks = missing[0]
|
||||||
|
print('No more ground drops; checking barrels for',
|
||||||
|
self.target_items)
|
||||||
self.state = self.find_barrels
|
self.state = self.find_barrels
|
||||||
return
|
return
|
||||||
|
|
||||||
print('Aborting, dont need any supplies')
|
print('Aborting, dont need any supplies')
|
||||||
self.state = self.cleanup
|
self.state = self.cleanup
|
||||||
|
|
||||||
|
def find_ground_items(self):
|
||||||
|
stack_count = self.ground_stack_counts.get(self.target_items, 0)
|
||||||
|
if self.target_maximum_stacks and stack_count >= self.target_maximum_stacks:
|
||||||
|
self.checked_ground_groups.add(self.target_items)
|
||||||
|
self.state = self.check_supplies
|
||||||
|
return
|
||||||
|
|
||||||
|
print('Looking for supplies on the ground...')
|
||||||
|
p = utils.pint(self.g.pos)
|
||||||
|
ground_items = self.g.world.find_objects(self.target_items)
|
||||||
|
ground_items.sort(key=lambda obj: utils.phyp(
|
||||||
|
p, (obj.x, obj.y, obj.z)))
|
||||||
|
|
||||||
|
for obj in ground_items:
|
||||||
|
if obj.entity_id in self.checked_ground_items:
|
||||||
|
continue
|
||||||
|
current_count = self.g.game.count_items(self.target_items)
|
||||||
|
if utils.phyp(p, (obj.x, obj.y, obj.z)) > 16:
|
||||||
|
continue
|
||||||
|
target = utils.pint((obj.x, obj.y, obj.z))
|
||||||
|
self.ground_entity_id = obj.entity_id
|
||||||
|
self.ground_position = target
|
||||||
|
self.ground_required_count = max(
|
||||||
|
self.target_minimum, current_count + 1)
|
||||||
|
if utils.phyp(p, (obj.x, obj.y, obj.z)) <= 1.25:
|
||||||
|
self.g.path = []
|
||||||
|
self.wait_time = 1.0
|
||||||
|
self.state = self.wait_for_ground_item
|
||||||
|
return
|
||||||
|
|
||||||
|
navpath = self.g.world.path_to_place(p, target)
|
||||||
|
if not navpath:
|
||||||
|
self.checked_ground_items.add(obj.entity_id)
|
||||||
|
continue
|
||||||
|
|
||||||
|
print('Going to ground item', obj.item_id, target)
|
||||||
|
self.g.path = navpath
|
||||||
|
self.state = self.going_to_ground_item
|
||||||
|
return
|
||||||
|
|
||||||
|
self.checked_ground_groups.add(self.target_items)
|
||||||
|
self.state = self.check_supplies
|
||||||
|
|
||||||
|
def going_to_ground_item(self):
|
||||||
|
obj = self.g.objects.get(self.ground_entity_id)
|
||||||
|
have_enough = (self.g.game.count_items(self.target_items) >=
|
||||||
|
self.ground_required_count)
|
||||||
|
if have_enough:
|
||||||
|
print('Picked up required ground supplies')
|
||||||
|
self._record_ground_pickup()
|
||||||
|
self.state = self.check_supplies
|
||||||
|
elif (obj is None or
|
||||||
|
utils.pint(self.g.pos) == self.ground_position or
|
||||||
|
utils.phyp(self.g.pos, (obj.x, obj.y, obj.z)) <= 1.25):
|
||||||
|
self.wait_time = 1.0
|
||||||
|
self.state = self.wait_for_ground_item
|
||||||
|
|
||||||
|
def wait_for_ground_item(self):
|
||||||
|
if (self.g.game.count_items(self.target_items) >=
|
||||||
|
self.ground_required_count):
|
||||||
|
print('Picked up required ground supplies')
|
||||||
|
self._record_ground_pickup()
|
||||||
|
self.state = self.check_supplies
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.wait_time > 0:
|
||||||
|
self.wait_time -= utils.TICK
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.ground_entity_id not in self.g.objects:
|
||||||
|
# Entity removal and inventory updates can arrive in either order.
|
||||||
|
self.state = self.check_supplies
|
||||||
|
return
|
||||||
|
|
||||||
|
print('Could not pick up ground item; trying another')
|
||||||
|
self.checked_ground_items.add(self.ground_entity_id)
|
||||||
|
self.g.path = []
|
||||||
|
self.ground_entity_id = None
|
||||||
|
self.ground_position = None
|
||||||
|
self.state = self.find_ground_items
|
||||||
|
|
||||||
def find_barrels(self):
|
def find_barrels(self):
|
||||||
print('Finding barrels...')
|
print('Finding barrels...')
|
||||||
w = self.g.world
|
w = self.g.world
|
||||||
@@ -215,7 +359,7 @@ class GrabSuppliesStates:
|
|||||||
self.g = global_state
|
self.g = global_state
|
||||||
self.state = self.idle
|
self.state = self.idle
|
||||||
|
|
||||||
self.supplies = {}
|
self.supplies = self.default_supplies()
|
||||||
self.barrels = []
|
self.barrels = []
|
||||||
self.checked_barrels = []
|
self.checked_barrels = []
|
||||||
self.bad_barrels = []
|
self.bad_barrels = []
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ from mosfet.job import JobStates
|
|||||||
class TestCropCaching(unittest.TestCase):
|
class TestCropCaching(unittest.TestCase):
|
||||||
def test_crop_farm_runs_silent_cache_machine_and_keeps_seeds(self):
|
def test_crop_farm_runs_silent_cache_machine_and_keeps_seeds(self):
|
||||||
old_wanted = items.WANTED_ITEMS
|
old_wanted = items.WANTED_ITEMS
|
||||||
|
supply_state = object()
|
||||||
crop_state = object()
|
crop_state = object()
|
||||||
cache_state = Munch(silent=False)
|
cache_state = Munch(silent=False)
|
||||||
sleep_state = Munch(silent=False)
|
sleep_state = Munch(silent=False)
|
||||||
eat_state = object()
|
eat_state = object()
|
||||||
jobs = JobStates.__new__(JobStates)
|
jobs = JobStates.__new__(JobStates)
|
||||||
|
jobs.grab_supplies_states = supply_state
|
||||||
jobs.gather_crop_states = crop_state
|
jobs.gather_crop_states = crop_state
|
||||||
jobs.cache_items_states = cache_state
|
jobs.cache_items_states = cache_state
|
||||||
jobs.sleep_with_bed_states = sleep_state
|
jobs.sleep_with_bed_states = sleep_state
|
||||||
@@ -22,7 +24,8 @@ class TestCropCaching(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
machines = JobStates.farm_crop(jobs)
|
machines = JobStates.farm_crop(jobs)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
machines, [crop_state, sleep_state, eat_state, cache_state])
|
machines, [supply_state, crop_state, sleep_state,
|
||||||
|
eat_state, cache_state])
|
||||||
self.assertTrue(cache_state.silent)
|
self.assertTrue(cache_state.silent)
|
||||||
self.assertTrue(sleep_state.silent)
|
self.assertTrue(sleep_state.silent)
|
||||||
self.assertTrue({
|
self.assertTrue({
|
||||||
|
|||||||
@@ -130,6 +130,9 @@ class GatherWoodPortTests(unittest.TestCase):
|
|||||||
])
|
])
|
||||||
self.assertEqual(job.grab_supplies_states.supplies, {
|
self.assertEqual(job.grab_supplies_states.supplies, {
|
||||||
tuple(items.AXE_IDS): (1, 9),
|
tuple(items.AXE_IDS): (1, 9),
|
||||||
|
tuple(items.BED_IDS): (1, 1),
|
||||||
|
(items.CHEST_ID,): (1, 1),
|
||||||
|
tuple(items.FOOD_IDS): (1, 1),
|
||||||
})
|
})
|
||||||
self.assertTrue(job.sleep_with_bed_states.silent)
|
self.assertTrue(job.sleep_with_bed_states.silent)
|
||||||
self.assertTrue(job.cache_items_states.silent)
|
self.assertTrue(job.cache_items_states.silent)
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import unittest
|
||||||
|
from munch import Munch
|
||||||
|
|
||||||
|
from mosfet.info import items
|
||||||
|
from mosfet.jobs.grab_supplies import GrabSuppliesStates
|
||||||
|
|
||||||
|
|
||||||
|
class GroundSupplyTests(unittest.TestCase):
|
||||||
|
def test_default_needed_supplies_are_configured_by_constructor(self):
|
||||||
|
state = GrabSuppliesStates(Munch())
|
||||||
|
self.assertEqual(state.supplies, {
|
||||||
|
tuple(items.BED_IDS): (1, 1),
|
||||||
|
(items.CHEST_ID,): (1, 1),
|
||||||
|
tuple(items.FOOD_IDS): (1, 1),
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_chest_drop_is_checked_even_when_bed_is_missing(self):
|
||||||
|
chest = Munch(entity_id=8, item_id=items.CHEST_ID,
|
||||||
|
x=2.0, y=64.0, z=0.0)
|
||||||
|
|
||||||
|
class FakeWorld:
|
||||||
|
@staticmethod
|
||||||
|
def find_objects(item_ids):
|
||||||
|
return [chest] if items.CHEST_ID in item_ids else []
|
||||||
|
|
||||||
|
class FakeGame:
|
||||||
|
@staticmethod
|
||||||
|
def count_inventory_slots():
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def count_items(_item_ids):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
state = GrabSuppliesStates(Munch(
|
||||||
|
pos=(0.5, 64.0, 0.5), world=FakeWorld(), game=FakeGame(),
|
||||||
|
maximum_supply_slots=33))
|
||||||
|
state.init()
|
||||||
|
state.check_supplies()
|
||||||
|
state.find_ground_items()
|
||||||
|
self.assertIn(tuple(items.BED_IDS), state.checked_ground_groups)
|
||||||
|
state.check_supplies()
|
||||||
|
|
||||||
|
self.assertEqual(state.target_items, (items.CHEST_ID,))
|
||||||
|
self.assertIs(state.state.__func__, state.find_ground_items.__func__)
|
||||||
|
|
||||||
|
def test_job_walks_to_and_picks_up_dropped_supply_before_barrels(self):
|
||||||
|
axe_id = next(iter(items.AXE_IDS))
|
||||||
|
dropped_axe = Munch(
|
||||||
|
entity_id=7, item_id=axe_id, item_count=1,
|
||||||
|
x=3.2, y=64.0, z=0.2)
|
||||||
|
|
||||||
|
class FakeWorld:
|
||||||
|
def __init__(self):
|
||||||
|
self.path_requests = []
|
||||||
|
|
||||||
|
def find_objects(self, item_ids):
|
||||||
|
return ([dropped_axe] if axe_id in item_ids else [])
|
||||||
|
|
||||||
|
def path_to_place(self, start, target):
|
||||||
|
self.path_requests.append((start, target))
|
||||||
|
return [start, target]
|
||||||
|
|
||||||
|
class FakeGame:
|
||||||
|
def __init__(self):
|
||||||
|
self.item_count = 0
|
||||||
|
|
||||||
|
def count_inventory_slots(self):
|
||||||
|
return 5
|
||||||
|
|
||||||
|
def count_items(self, item_ids):
|
||||||
|
return self.item_count if axe_id in item_ids else 0
|
||||||
|
|
||||||
|
world = FakeWorld()
|
||||||
|
game = FakeGame()
|
||||||
|
g = Munch(
|
||||||
|
pos=(0.5, 64.0, 0.5), world=world, game=game,
|
||||||
|
chunks=Munch(), objects={7: dropped_axe}, path=[],
|
||||||
|
maximum_supply_slots=33,
|
||||||
|
)
|
||||||
|
state = GrabSuppliesStates(g)
|
||||||
|
state.supplies = {tuple(items.AXE_IDS): (1, 9)}
|
||||||
|
|
||||||
|
state.init()
|
||||||
|
state.check_supplies()
|
||||||
|
state.find_ground_items()
|
||||||
|
self.assertIs(state.state.__func__, state.going_to_ground_item.__func__)
|
||||||
|
self.assertEqual(g.path[-1], (3, 64, 0))
|
||||||
|
self.assertEqual(world.path_requests, [((0, 64, 0), (3, 64, 0))])
|
||||||
|
|
||||||
|
g.pos = (3.5, 64.0, 0.5)
|
||||||
|
state.going_to_ground_item()
|
||||||
|
self.assertIs(state.state.__func__, state.wait_for_ground_item.__func__)
|
||||||
|
|
||||||
|
game.item_count = 1
|
||||||
|
state.wait_for_ground_item()
|
||||||
|
self.assertIs(state.state.__func__, state.check_supplies.__func__)
|
||||||
|
state.check_supplies()
|
||||||
|
self.assertIs(state.state.__func__, state.cleanup.__func__)
|
||||||
|
|
||||||
|
# Nearby dropped tools are collected even when inventory already meets
|
||||||
|
# the minimum, so tossing an axe during setup still gets noticed.
|
||||||
|
game.item_count = 1
|
||||||
|
state.init()
|
||||||
|
state.check_supplies()
|
||||||
|
self.assertIs(state.state.__func__, state.find_ground_items.__func__)
|
||||||
|
state.find_ground_items()
|
||||||
|
self.assertEqual(state.ground_required_count, 2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user