azalea/pathfinder/
goals.rs

1//! The goals that a pathfinder can try to reach.
2
3use std::{f32::consts::SQRT_2, fmt::Debug};
4
5use azalea_core::position::{BlockPos, Vec3};
6use azalea_world::ChunkStorage;
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10use super::costs::{COST_HEURISTIC, FALL_N_BLOCKS_COST, JUMP_ONE_BLOCK_COST};
11
12pub trait Goal: Debug {
13    #[must_use]
14    fn heuristic(&self, n: BlockPos) -> f32;
15    #[must_use]
16    fn success(&self, n: BlockPos) -> bool;
17}
18
19/// Move to the given block position.
20#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
21#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
22pub struct BlockPosGoal(pub BlockPos);
23impl Goal for BlockPosGoal {
24    fn heuristic(&self, n: BlockPos) -> f32 {
25        let dx = (self.0.x - n.x) as f32;
26        let dy = (self.0.y - n.y) as f32;
27        let dz = (self.0.z - n.z) as f32;
28
29        xz_heuristic(dx, dz) + y_heuristic(dy)
30    }
31    fn success(&self, n: BlockPos) -> bool {
32        n == self.0
33    }
34}
35
36fn xz_heuristic(dx: f32, dz: f32) -> f32 {
37    let x = dx.abs();
38    let z = dz.abs();
39
40    let diagonal;
41    let straight;
42
43    if x < z {
44        straight = z - x;
45        diagonal = x;
46    } else {
47        straight = x - z;
48        diagonal = z;
49    }
50
51    (diagonal * SQRT_2 + straight) * COST_HEURISTIC
52}
53
54/// Move to the given block position, ignoring the y-axis.
55#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
56#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
57pub struct XZGoal {
58    pub x: i32,
59    pub z: i32,
60}
61impl Goal for XZGoal {
62    fn heuristic(&self, n: BlockPos) -> f32 {
63        let dx = (self.x - n.x) as f32;
64        let dz = (self.z - n.z) as f32;
65        xz_heuristic(dx, dz)
66    }
67    fn success(&self, n: BlockPos) -> bool {
68        n.x == self.x && n.z == self.z
69    }
70}
71
72fn y_heuristic(dy: f32) -> f32 {
73    if dy > 0.0 {
74        *JUMP_ONE_BLOCK_COST * dy
75    } else {
76        FALL_N_BLOCKS_COST[2] / 2. * -dy
77    }
78}
79
80/// Move to the given y coordinate.
81#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
82#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
83pub struct YGoal {
84    pub y: i32,
85}
86impl Goal for YGoal {
87    fn heuristic(&self, n: BlockPos) -> f32 {
88        let dy = (self.y - n.y) as f32;
89        y_heuristic(dy)
90    }
91    fn success(&self, n: BlockPos) -> bool {
92        n.y == self.y
93    }
94}
95
96/// Get within the given radius of the given position.
97#[derive(Clone, Copy, Debug, Default, PartialEq)]
98#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
99pub struct RadiusGoal {
100    pub pos: Vec3,
101    pub radius: f32,
102}
103impl Goal for RadiusGoal {
104    fn heuristic(&self, n: BlockPos) -> f32 {
105        let n = n.center();
106        let dx = (self.pos.x - n.x) as f32;
107        let dy = (self.pos.y - n.y) as f32;
108        let dz = (self.pos.z - n.z) as f32;
109        dx * dx + dy * dy + dz * dz
110    }
111    fn success(&self, n: BlockPos) -> bool {
112        let n = n.center();
113        let dx = (self.pos.x - n.x) as f32;
114        let dy = (self.pos.y - n.y) as f32;
115        let dz = (self.pos.z - n.z) as f32;
116        dx * dx + dy * dy + dz * dz <= self.radius * self.radius
117    }
118}
119
120/// Do the opposite of the given goal.
121#[derive(Debug)]
122pub struct InverseGoal<T: Goal>(pub T);
123impl<T: Goal> Goal for InverseGoal<T> {
124    fn heuristic(&self, n: BlockPos) -> f32 {
125        -self.0.heuristic(n)
126    }
127    fn success(&self, n: BlockPos) -> bool {
128        !self.0.success(n)
129    }
130}
131
132/// Do either of the given goals, whichever is closer.
133#[derive(Debug)]
134pub struct OrGoal<T: Goal, U: Goal>(pub T, pub U);
135impl<T: Goal, U: Goal> Goal for OrGoal<T, U> {
136    fn heuristic(&self, n: BlockPos) -> f32 {
137        self.0.heuristic(n).min(self.1.heuristic(n))
138    }
139    fn success(&self, n: BlockPos) -> bool {
140        self.0.success(n) || self.1.success(n)
141    }
142}
143
144/// Do any of the given goals, whichever is closest.
145#[derive(Debug)]
146pub struct OrGoals<T: Goal>(pub Vec<T>);
147impl<T: Goal> Goal for OrGoals<T> {
148    fn heuristic(&self, n: BlockPos) -> f32 {
149        self.0
150            .iter()
151            .map(|goal| goal.heuristic(n))
152            .min_by(|a, b| a.partial_cmp(b).unwrap())
153            .unwrap_or(f32::INFINITY)
154    }
155    fn success(&self, n: BlockPos) -> bool {
156        self.0.iter().any(|goal| goal.success(n))
157    }
158}
159
160/// Try to reach both of the given goals.
161#[derive(Debug)]
162pub struct AndGoal<T: Goal, U: Goal>(pub T, pub U);
163impl<T: Goal, U: Goal> Goal for AndGoal<T, U> {
164    fn heuristic(&self, n: BlockPos) -> f32 {
165        self.0.heuristic(n).max(self.1.heuristic(n))
166    }
167    fn success(&self, n: BlockPos) -> bool {
168        self.0.success(n) && self.1.success(n)
169    }
170}
171
172/// Try to reach all the given goals.
173#[derive(Debug)]
174pub struct AndGoals<T: Goal>(pub Vec<T>);
175impl<T: Goal> Goal for AndGoals<T> {
176    fn heuristic(&self, n: BlockPos) -> f32 {
177        self.0
178            .iter()
179            .map(|goal| goal.heuristic(n))
180            .max_by(|a, b| a.partial_cmp(b).unwrap())
181            .unwrap_or(f32::INFINITY)
182    }
183    fn success(&self, n: BlockPos) -> bool {
184        self.0.iter().all(|goal| goal.success(n))
185    }
186}
187
188/// Move to a position where we can reach the given block.
189#[derive(Clone, Debug)]
190pub struct ReachBlockPosGoal {
191    pub pos: BlockPos,
192    pub chunk_storage: ChunkStorage,
193}
194impl Goal for ReachBlockPosGoal {
195    fn heuristic(&self, n: BlockPos) -> f32 {
196        BlockPosGoal(self.pos).heuristic(n)
197    }
198    fn success(&self, n: BlockPos) -> bool {
199        // only do the expensive check if we're close enough
200        let max_pick_range = 6;
201        let actual_pick_range = 4.5;
202
203        let distance = (self.pos - n).length_squared();
204        if distance > max_pick_range * max_pick_range {
205            return false;
206        }
207
208        let eye_position = n.to_vec3_floored() + Vec3::new(0.5, 1.62, 0.5);
209        let look_direction = crate::direction_looking_at(&eye_position, &self.pos.center());
210        let block_hit_result = azalea_client::interact::pick(
211            &look_direction,
212            &eye_position,
213            &self.chunk_storage,
214            actual_pick_range,
215        );
216
217        block_hit_result.block_pos == self.pos
218    }
219}