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};
14
15pub trait Goal: Debug + Send + Sync {
16    #[must_use]
17    fn heuristic(&self, n: BlockPos) -> f32;
18    #[must_use]
19    fn success(&self, n: BlockPos) -> bool;
20}
21
22/// Move to the given block position.
23#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
24#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
25pub struct BlockPosGoal(pub BlockPos);
26impl Goal for BlockPosGoal {
27    fn heuristic(&self, n: BlockPos) -> f32 {
28        let dx = (self.0.x - n.x) as f32;
29        let dy = (self.0.y - n.y) as f32;
30        let dz = (self.0.z - n.z) as f32;
31
32        xz_heuristic(dx, dz) + y_heuristic(dy)
33    }
34    fn success(&self, n: BlockPos) -> bool {
35        // the second half of this condition is intended to fix issues when pathing to
36        // non-full blocks
37        n == self.0 || n.down(1) == self.0
38    }
39}
40
41fn xz_heuristic(dx: f32, dz: f32) -> f32 {
42    let x = dx.abs();
43    let z = dz.abs();
44
45    let diagonal;
46    let straight;
47
48    if x < z {
49        straight = z - x;
50        diagonal = x;
51    } else {
52        straight = x - z;
53        diagonal = z;
54    }
55
56    (diagonal * SQRT_2 + straight) * COST_HEURISTIC
57}
58
59/// Move to the given block position, ignoring the y-axis.
60#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
61#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
62pub struct XZGoal {
63    pub x: i32,
64    pub z: i32,
65}
66impl Goal for XZGoal {
67    fn heuristic(&self, n: BlockPos) -> f32 {
68        let dx = (self.x - n.x) as f32;
69        let dz = (self.z - n.z) as f32;
70        xz_heuristic(dx, dz)
71    }
72    fn success(&self, n: BlockPos) -> bool {
73        n.x == self.x && n.z == self.z
74    }
75}
76
77fn y_heuristic(dy: f32) -> f32 {
78    if dy > 0.0 {
79        *JUMP_ONE_BLOCK_COST * dy
80    } else {
81        FALL_N_BLOCKS_COST[2] / 2. * -dy
82    }
83}
84
85/// Move to the given y coordinate.
86#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
87#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
88pub struct YGoal {
89    pub y: i32,
90}
91impl Goal for YGoal {
92    fn heuristic(&self, n: BlockPos) -> f32 {
93        let dy = (self.y - n.y) as f32;
94        y_heuristic(dy)
95    }
96    fn success(&self, n: BlockPos) -> bool {
97        n.y == self.y
98    }
99}
100
101/// Get within the given radius of the given position.
102#[derive(Clone, Copy, Debug, Default, PartialEq)]
103#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
104pub struct RadiusGoal {
105    pub pos: Vec3,
106    pub radius: f32,
107}
108impl RadiusGoal {
109    pub fn new(pos: Vec3, radius: f32) -> Self {
110        Self { pos, radius }
111    }
112}
113impl Goal for RadiusGoal {
114    fn heuristic(&self, n: BlockPos) -> f32 {
115        let n = n.center();
116        let dx = (self.pos.x - n.x) as f32;
117        let dy = (self.pos.y - n.y) as f32;
118        let dz = (self.pos.z - n.z) as f32;
119
120        xz_heuristic(dx, dz) + y_heuristic(dy)
121    }
122    fn success(&self, n: BlockPos) -> bool {
123        let n = n.center();
124        let dx = (self.pos.x - n.x) as f32;
125        let dy = (self.pos.y - n.y) as f32;
126        let dz = (self.pos.z - n.z) as f32;
127        dx.powi(2) + dy.powi(2) + dz.powi(2) <= self.radius.powi(2)
128    }
129}
130
131/// Do the opposite of the given goal.
132#[derive(Debug)]
133#[deprecated = "`InverseGoal` has poor performance and often doesn't work as expected, consider using different goals."]
134pub struct InverseGoal<T: Goal>(pub T);
135#[allow(deprecated)]
136impl<T: Goal> Goal for InverseGoal<T> {
137    fn heuristic(&self, n: BlockPos) -> f32 {
138        -self.0.heuristic(n)
139    }
140    fn success(&self, n: BlockPos) -> bool {
141        !self.0.success(n)
142    }
143}
144
145/// Do either of the given goals, whichever is closer.
146#[derive(Debug)]
147pub struct OrGoal<T: Goal, U: Goal>(pub T, pub U);
148impl<T: Goal, U: Goal> Goal for OrGoal<T, U> {
149    fn heuristic(&self, n: BlockPos) -> f32 {
150        self.0.heuristic(n).min(self.1.heuristic(n))
151    }
152    fn success(&self, n: BlockPos) -> bool {
153        self.0.success(n) || self.1.success(n)
154    }
155}
156
157/// Do any of the given goals, whichever is closest.
158#[derive(Debug)]
159pub struct OrGoals<T: Goal>(pub Vec<T>);
160impl<T: Goal> Goal for OrGoals<T> {
161    fn heuristic(&self, n: BlockPos) -> f32 {
162        self.0
163            .iter()
164            .map(|goal| goal.heuristic(n))
165            .min_by(|a, b| a.partial_cmp(b).unwrap())
166            .unwrap_or(f32::INFINITY)
167    }
168    fn success(&self, n: BlockPos) -> bool {
169        self.0.iter().any(|goal| goal.success(n))
170    }
171}
172
173/// Try to reach both of the given goals.
174#[derive(Debug)]
175pub struct AndGoal<T: Goal, U: Goal>(pub T, pub U);
176impl<T: Goal, U: Goal> Goal for AndGoal<T, U> {
177    fn heuristic(&self, n: BlockPos) -> f32 {
178        self.0.heuristic(n).max(self.1.heuristic(n))
179    }
180    fn success(&self, n: BlockPos) -> bool {
181        self.0.success(n) && self.1.success(n)
182    }
183}
184
185/// Try to reach all the given goals.
186#[derive(Debug)]
187pub struct AndGoals<T: Goal>(pub Vec<T>);
188impl<T: Goal> Goal for AndGoals<T> {
189    fn heuristic(&self, n: BlockPos) -> f32 {
190        self.0
191            .iter()
192            .map(|goal| goal.heuristic(n))
193            .max_by(|a, b| a.partial_cmp(b).unwrap())
194            .unwrap_or(f32::INFINITY)
195    }
196    fn success(&self, n: BlockPos) -> bool {
197        self.0.iter().all(|goal| goal.success(n))
198    }
199}
200
201/// Move to a position where we can reach the given block.
202#[derive(Clone)]
203pub struct ReachBlockPosGoal {
204    pub pos: BlockPos,
205    pub distance: f64,
206    pub chunk_storage: ChunkStorage,
207
208    max_check_distance: i32,
209}
210impl ReachBlockPosGoal {
211    pub fn new(pos: BlockPos, chunk_storage: ChunkStorage) -> Self {
212        Self::new_with_distance(pos, 4.5, chunk_storage)
213    }
214
215    pub fn new_with_distance(pos: BlockPos, distance: f64, chunk_storage: ChunkStorage) -> Self {
216        Self {
217            pos,
218            distance,
219            chunk_storage,
220            max_check_distance: (distance + 2.).ceil() as i32,
221        }
222    }
223}
224impl Goal for ReachBlockPosGoal {
225    fn heuristic(&self, n: BlockPos) -> f32 {
226        BlockPosGoal(self.pos).heuristic(n)
227    }
228    fn success(&self, n: BlockPos) -> bool {
229        let head = n.up(1);
230        // the player's head is in the block or adjacent to it, so assume that it's
231        // always reachable (we do this to account for mining)
232        if head == self.pos
233            || head.north(1) == self.pos
234            || head.south(1) == self.pos
235            || head.east(1) == self.pos
236            || head.west(1) == self.pos
237            || head.up(1) == self.pos
238            || head.down(1) == self.pos
239        {
240            return true;
241        }
242
243        // only do the expensive check if we're close enough
244        let distance_squared = self.pos.distance_squared_to(n);
245        if distance_squared > self.max_check_distance.pow(2) {
246            return false;
247        }
248
249        let eye_position = n.center_bottom().up(1.62);
250        let look_direction = crate::bot::direction_looking_at(eye_position, self.pos.center());
251        let block_hit_result = azalea_client::interact::pick::pick_block(
252            look_direction,
253            eye_position,
254            &self.chunk_storage,
255            self.distance,
256        );
257
258        block_hit_result.block_pos == self.pos
259    }
260}
261impl Debug for ReachBlockPosGoal {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        f.debug_struct("ReachBlockPosGoal")
264            .field("pos", &self.pos)
265            .field("distance", &self.distance)
266            .field("max_check_distance", &self.max_check_distance)
267            .finish()
268    }
269}