diff --git a/mosfet/bot.py b/mosfet/bot.py index 41dd2bb..60d8a4c 100644 --- a/mosfet/bot.py +++ b/mosfet/bot.py @@ -57,26 +57,25 @@ PITCH_ANGLE_DIR = vector.Vector3D((0, 1, 0)) YAW_ANGLE_DIR = vector.Vector3D((0, 0, -1)) YAW_ANGLE_REF = vector.Vector3D((0, 1, 0)) YAW_LOOK_AHEAD = 4 -FARMING_PITCH = 45.0 +PLAYER_EYE_HEIGHT = 1.62 +MAX_DOWNWARD_PITCH = 45.0 -def pitch_lock_for_job(job_state): - state = getattr(job_state, 'state', None) if job_state else None - return (FARMING_PITCH - if getattr(state, '__name__', None) in ('farm_crop', 'farm_wart') - else None) +def look_direction_from_eye(position, target): + eye = vector.Point3D((position.x, position.y + PLAYER_EYE_HEIGHT, + position.z)) + return eye - target -def update_pitch(g, look_at_d, fixed_pitch=None): - if fixed_pitch is not None: - target_pitch = fixed_pitch - elif look_at_d is None: +def update_pitch(g, look_at_d): + if look_at_d is None: target_pitch = 0.0 elif look_at_d.length() > 0.6: target_pitch = look_at_d.normalized().angleDeg(PITCH_ANGLE_DIR) target_pitch = (target_pitch - 90) * -1 else: return + target_pitch = min(target_pitch, MAX_DOWNWARD_PITCH) g.pitch += utils.cap(target_pitch - g.pitch, 10) @@ -239,12 +238,12 @@ def tick(global_state): else: look_at = None - pitch_lock = pitch_lock_for_job(g.get('job')) if look_at: look_at.x += 0.5 + look_at.y += 0.5 look_at.z += 0.5 look_at_d = p - look_at - update_pitch(g, look_at_d, pitch_lock) + update_pitch(g, look_direction_from_eye(p, look_at)) # remove vertical component for yaw calculation look_at_d.y = 0 @@ -255,7 +254,7 @@ def tick(global_state): target_yaw_d = (target_yaw_d + 180) % 360 - 180 g.yaw += utils.cap(target_yaw_d, 30) else: - update_pitch(g, None, pitch_lock) + update_pitch(g, None) from minecraft.networking.modern import ( ClientTickEnd, PlayerPositionRotation, diff --git a/mosfet/jobs/gather_crop.py b/mosfet/jobs/gather_crop.py index 1beac21..7d5879d 100644 --- a/mosfet/jobs/gather_crop.py +++ b/mosfet/jobs/gather_crop.py @@ -129,7 +129,7 @@ class GatherCropStates: if navpath: print('Going to crop', self.crop) self.g.path = navpath - self.g.look_at = utils.padd(self.crop, path.BLOCK_BELOW) + self.g.look_at = self.crop self.state = self.going_to_crop else: print('Cant get to it, blacklisting') diff --git a/mosfet/jobs/gather_wart.py b/mosfet/jobs/gather_wart.py index cd70c0c..db82122 100644 --- a/mosfet/jobs/gather_wart.py +++ b/mosfet/jobs/gather_wart.py @@ -51,7 +51,7 @@ class GatherWartStates: if navpath: print('Going to wart', self.wart) self.g.path = navpath - self.g.look_at = utils.padd(self.wart, path.BLOCK_BELOW) + self.g.look_at = self.wart self.state = self.going_to_wart else: print('Cant get to it, blacklisting') diff --git a/tests/test_crop_batch.py b/tests/test_crop_batch.py index 96f3b5e..9a688a0 100644 --- a/tests/test_crop_batch.py +++ b/tests/test_crop_batch.py @@ -6,6 +6,23 @@ from mosfet.jobs.gather_crop import GatherCropStates class CropBatchTests(unittest.TestCase): + def test_navigation_looks_at_the_crop_target(self): + crop = (5, 64, 8) + + class FakeWorld: + @staticmethod + def path_to_place(_start, _target): + return [(1, 64, 1), crop] + + g = Munch(world=FakeWorld(), pos=(1, 64, 1), path=[], look_at=None) + state = GatherCropStates(g) + state.crop = crop + + state.nav_to_crop() + + self.assertEqual(g.look_at, crop) + self.assertEqual(g.path[-1], crop) + def test_gather_crop_finishes_after_sixteen_successful_replants(self): unripe_wheat = blocks.MCD_BLOCKS['wheat']['minStateId'] mature_wheat = blocks.MATURE_WHEAT_ID diff --git a/tests/test_farming_pitch.py b/tests/test_farming_pitch.py index 67858c5..f836a45 100644 --- a/tests/test_farming_pitch.py +++ b/tests/test_farming_pitch.py @@ -2,37 +2,56 @@ import unittest from munch import Munch -from mosfet.bot import FARMING_PITCH, pitch_lock_for_job, update_pitch +from mosfet.bot import look_direction_from_eye, update_pitch +from mosfet.vector import Point3D + + +class LookDirection: + def __init__(self, angle): + self.angle = angle + + def length(self): + return 2.0 + + def normalized(self): + return self + + def angleDeg(self, _direction): + return self.angle class TestFarmingPitch(unittest.TestCase): - def test_crop_and_wart_farming_lock_pitch(self): - def farm_crop(): - pass + def test_aiming_at_a_same_level_crop_uses_eye_height_and_looks_down(self): + player = Point3D((0.5, 64.0, 0.5)) + crop_center = Point3D((1.5, 64.5, 1.5)) + direction = look_direction_from_eye(player, crop_center) - def farm_wart(): - pass - - def sleep(): - pass - - self.assertEqual( - 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(None)) - - def test_pitch_is_held_at_45_despite_look_target_changes(self): + self.assertGreater(direction.y, 0) state = Munch(pitch=0.0) - for _ in range(5): - update_pitch(state, object(), FARMING_PITCH) + update_pitch(state, direction) + self.assertGreater(state.pitch, 0.0) + + def test_pitch_never_looks_more_than_45_degrees_down(self): + state = Munch(pitch=0.0) + directly_below = LookDirection(0.0) + for _ in range(8): + update_pitch(state, directly_below) self.assertEqual(state.pitch, 45.0) - # Without the farming lock, ordinary look behavior is restored. - update_pitch(state, None) + def test_pitch_tracks_look_target_instead_of_a_fixed_farming_angle(self): + state = Munch(pitch=0.0) + direction_to_crop = LookDirection(45.0) + for _ in range(5): + update_pitch(state, direction_to_crop) + self.assertEqual(state.pitch, 45.0) + + # Looking level at another target pulls the pitch back toward zero. + update_pitch(state, LookDirection(90.0)) self.assertEqual(state.pitch, 35.0) + update_pitch(state, None) + self.assertEqual(state.pitch, 25.0) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_gather_wart.py b/tests/test_gather_wart.py index 214c685..d0a8de4 100644 --- a/tests/test_gather_wart.py +++ b/tests/test_gather_wart.py @@ -120,6 +120,23 @@ class GatherWartTests(unittest.TestCase): state.wait_place() self.assertIs(state.state.__func__, state.cleanup.__func__) + def test_navigation_looks_at_the_wart_target(self): + position = (5, 64, 8) + + class FakeWorld: + @staticmethod + def path_to_place(_start, _target): + return [(1, 64, 1), position] + + g = Munch(world=FakeWorld(), pos=(1, 64, 1), path=[], look_at=None) + state = GatherWartStates(g) + state.wart = position + + state.nav_to_wart() + + self.assertEqual(g.look_at, position) + self.assertEqual(g.path[-1], position) + def test_unreachable_wart_is_blacklisted_without_undefined_variable(self): position = (5, 64, 8)