Skip to main content

azalea/pathfinder/
goals.rs

1//! The goals that a pathfinder can try to reach.
2
3use std::{
4    f32::consts::SQRT_2,
5    fmt::{self, Debug},
6};
7
8use azalea_core::position::{BlockPos, Vec3};
9use azalea_world::ChunkStorage;
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13use super::costs::{COST_HEURISTIC, FALL_N_BLOCKS_COST, JUMP_ONE_BLOCK_COST};
14use crate::pathfinder::{
15    costs::{JUMP_PENALTY, SPRINT_ONE_BLOCK_COST, WALK_ONE_BLOCK_COST},
16    moves::BARITONE_COMPAT,
17};
18
19pub trait Goal: Debug + Send + Sync {
20    #[must_use]
21    fn heuristic(&self, n: BlockPos) -> f32;
22    #[must_use]
23    fn success(&self, n: BlockPos) -> bool;
24}
25
26/// Move to the given block position.
27///
28/// If you're converting to a `BlockPosGoal` from a [`Vec3`], you should move
29/// the Y up by 0.5 to avoid the pathfinding destroying the final block if it's
30/// non-full like farmland.
31///
32/// ```
33/// # use azalea::{prelude::*, pathfinder::goals::BlockPosGoal};
34/// # fn example(bot: &Client, entity_position: azalea::Vec3) {
35/// bot.start_goto(BlockPosGoal(entity_position.up(0.5).into()));
36/// # }
37/// ```
38#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
39#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
40pub struct BlockPosGoal(pub BlockPos);
41impl Goal for BlockPosGoal {
42    fn heuristic(&self, n: BlockPos) -> f32 {
43        let dx = (self.0.x - n.x) as f32;
44        let dy = (self.0.y - n.y) as f32;
45        let dz = (self.0.z - n.z) as f32;
46
47        xz_heuristic(dx, dz) + y_heuristic(dy)
48    }
49    fn success(&self, n: BlockPos) -> bool {
50        n == self.0
51    }
52}
53
54fn xz_heuristic(dx: f32, dz: f32) -> f32 {
55    let x = dx.abs();
56    let z = dz.abs();
57
58    let diagonal;
59    let straight;
60
61    if x < z {
62        straight = z - x;
63        diagonal = x;
64    } else {
65        straight = x - z;
66        diagonal = z;
67    }
68
69    (diagonal * SQRT_2 + straight) * COST_HEURISTIC
70}
71
72/// Move to the given block position, ignoring the y-axis.
73#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
74#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
75pub struct XZGoal {
76    pub x: i32,
77    pub z: i32,
78}
79impl Goal for XZGoal {
80    fn heuristic(&self, n: BlockPos) -> f32 {
81        let dx = (self.x - n.x) as f32;
82        let dz = (self.z - n.z) as f32;
83        xz_heuristic(dx, dz)
84    }
85    fn success(&self, n: BlockPos) -> bool {
86        n.x == self.x && n.z == self.z
87    }
88}
89impl From<BlockPos> for XZGoal {
90    fn from(block: BlockPos) -> Self {
91        Self {
92            x: block.x,
93            z: block.z,
94        }
95    }
96}
97
98fn y_heuristic(dy: f32) -> f32 {
99    if dy > 0. {
100        if BARITONE_COMPAT {
101            // this is wrong because it can be an overestimate
102            return *JUMP_ONE_BLOCK_COST * dy;
103        }
104
105        // forward+up is the main way to ascend, so make sure to match that. we subtract
106        // SPRINT_ONE_BLOCK_COST to cancel out the value that should've been
107        // added by the xz heuristic.
108        (f32::max(*JUMP_ONE_BLOCK_COST, WALK_ONE_BLOCK_COST) + JUMP_PENALTY - SPRINT_ONE_BLOCK_COST)
109            * dy
110    } else if dy < 0. {
111        // this is also an overestimate (copied from baritone), but fixing it makes perf
112        // too much worse
113        (FALL_N_BLOCKS_COST[2] / 2.) * -dy
114    } else {
115        0.
116    }
117}
118
119/// Move to the given y coordinate.
120#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
121#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
122pub struct YGoal {
123    pub y: i32,
124}
125impl Goal for YGoal {
126    fn heuristic(&self, n: BlockPos) -> f32 {
127        let dy = (self.y - n.y) as f32;
128        y_heuristic(dy)
129    }
130    fn success(&self, n: BlockPos) -> bool {
131        n.y == self.y
132    }
133}
134impl From<BlockPos> for YGoal {
135    fn from(block: BlockPos) -> Self {
136        Self { y: block.y }
137    }
138}
139
140/// Get within the given radius of the given position.
141#[derive(Clone, Copy, Debug, Default, PartialEq)]
142#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
143pub struct RadiusGoal {
144    pub pos: Vec3,
145    pub radius: f32,
146}
147impl RadiusGoal {
148    pub fn new(pos: Vec3, radius: f32) -> Self {
149        Self { pos, radius }
150    }
151}
152impl Goal for RadiusGoal {
153    fn heuristic(&self, n: BlockPos) -> f32 {
154        let n = n.center();
155        let dx = (self.pos.x - n.x) as f32;
156        let dy = (self.pos.y - n.y) as f32;
157        let dz = (self.pos.z - n.z) as f32;
158
159        xz_heuristic(dx, dz) + y_heuristic(dy)
160    }
161    fn success(&self, n: BlockPos) -> bool {
162        let n = n.center();
163        let dx = (self.pos.x - n.x) as f32;
164        let dy = (self.pos.y - n.y) as f32;
165        let dz = (self.pos.z - n.z) as f32;
166        dx.powi(2) + dy.powi(2) + dz.powi(2) <= self.radius.powi(2)
167    }
168}
169
170/// Do the opposite of the given goal.
171#[derive(Debug)]
172#[deprecated = "`InverseGoal` has poor performance and often doesn't work as expected, consider using different goals."]
173pub struct InverseGoal<T: Goal>(pub T);
174#[allow(deprecated)]
175impl<T: Goal> Goal for InverseGoal<T> {
176    fn heuristic(&self, n: BlockPos) -> f32 {
177        -self.0.heuristic(n)
178    }
179    fn success(&self, n: BlockPos) -> bool {
180        !self.0.success(n)
181    }
182}
183
184/// Do either of the given goals, whichever is closer.
185#[derive(Debug)]
186pub struct OrGoal<T: Goal, U: Goal>(pub T, pub U);
187impl<T: Goal, U: Goal> Goal for OrGoal<T, U> {
188    fn heuristic(&self, n: BlockPos) -> f32 {
189        self.0.heuristic(n).min(self.1.heuristic(n))
190    }
191    fn success(&self, n: BlockPos) -> bool {
192        self.0.success(n) || self.1.success(n)
193    }
194}
195
196/// Do any of the given goals, whichever is closest.
197#[derive(Debug)]
198pub struct OrGoals<T: Goal>(pub Vec<T>);
199impl<T: Goal> Goal for OrGoals<T> {
200    fn heuristic(&self, n: BlockPos) -> f32 {
201        self.0
202            .iter()
203            .map(|goal| goal.heuristic(n))
204            .min_by(|a, b| a.partial_cmp(b).unwrap())
205            .unwrap_or(f32::INFINITY)
206    }
207    fn success(&self, n: BlockPos) -> bool {
208        self.0.iter().any(|goal| goal.success(n))
209    }
210}
211
212/// Try to reach both of the given goals.
213#[derive(Debug)]
214pub struct AndGoal<T: Goal, U: Goal>(pub T, pub U);
215impl<T: Goal, U: Goal> Goal for AndGoal<T, U> {
216    fn heuristic(&self, n: BlockPos) -> f32 {
217        self.0.heuristic(n).max(self.1.heuristic(n))
218    }
219    fn success(&self, n: BlockPos) -> bool {
220        self.0.success(n) && self.1.success(n)
221    }
222}
223
224/// Try to reach all the given goals.
225#[derive(Debug)]
226pub struct AndGoals<T: Goal>(pub Vec<T>);
227impl<T: Goal> Goal for AndGoals<T> {
228    fn heuristic(&self, n: BlockPos) -> f32 {
229        self.0
230            .iter()
231            .map(|goal| goal.heuristic(n))
232            .max_by(|a, b| a.partial_cmp(b).unwrap())
233            .unwrap_or(f32::INFINITY)
234    }
235    fn success(&self, n: BlockPos) -> bool {
236        self.0.iter().all(|goal| goal.success(n))
237    }
238}
239
240/// Move to a position where we can reach the given block.
241#[derive(Clone)]
242pub struct ReachBlockPosGoal {
243    pub pos: BlockPos,
244    pub distance: f64,
245    pub chunk_storage: ChunkStorage,
246
247    max_check_distance: i32,
248}
249impl ReachBlockPosGoal {
250    pub fn new(pos: BlockPos, chunk_storage: ChunkStorage) -> Self {
251        Self::new_with_distance(pos, 4.5, chunk_storage)
252    }
253
254    pub fn new_with_distance(pos: BlockPos, distance: f64, chunk_storage: ChunkStorage) -> Self {
255        Self {
256            pos,
257            distance,
258            chunk_storage,
259            max_check_distance: (distance + 2.).ceil() as i32,
260        }
261    }
262}
263impl Goal for ReachBlockPosGoal {
264    fn heuristic(&self, n: BlockPos) -> f32 {
265        BlockPosGoal(self.pos).heuristic(n)
266    }
267    fn success(&self, n: BlockPos) -> bool {
268        let head = n.up(1);
269        // the player's head is in the block or adjacent to it, so assume that it's
270        // always reachable (we do this to account for mining)
271        if head == self.pos
272            || head.north(1) == self.pos
273            || head.south(1) == self.pos
274            || head.east(1) == self.pos
275            || head.west(1) == self.pos
276            || head.up(1) == self.pos
277            || head.down(1) == self.pos
278        {
279            return true;
280        }
281
282        // only do the expensive check if we're close enough
283        let distance_squared = self.pos.distance_squared_to(n);
284        if distance_squared > self.max_check_distance.pow(2) {
285            return false;
286        }
287
288        let eye_position = n.center_bottom().up(1.62);
289        let look_direction = crate::bot::direction_looking_at(eye_position, self.pos.center());
290        let block_hit_result = azalea_client::interact::pick::pick_block(
291            look_direction,
292            eye_position,
293            &self.chunk_storage,
294            self.distance,
295        );
296
297        block_hit_result.block_pos == self.pos
298    }
299}
300impl Debug for ReachBlockPosGoal {
301    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302        f.debug_struct("ReachBlockPosGoal")
303            .field("pos", &self.pos)
304            .field("distance", &self.distance)
305            .field("max_check_distance", &self.max_check_distance)
306            .finish()
307    }
308}