1use 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#[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, straight) = if x < z { (x, z - x) } else { (z, x - z) };
60
61 (diagonal * SQRT_2 + straight) * COST_HEURISTIC
62}
63
64#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
66#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
67pub struct XZGoal {
68 pub x: i32,
69 pub z: i32,
70}
71impl Goal for XZGoal {
72 fn heuristic(&self, n: BlockPos) -> f32 {
73 let dx = (self.x - n.x) as f32;
74 let dz = (self.z - n.z) as f32;
75 xz_heuristic(dx, dz)
76 }
77 fn success(&self, n: BlockPos) -> bool {
78 n.x == self.x && n.z == self.z
79 }
80}
81impl From<BlockPos> for XZGoal {
82 fn from(block: BlockPos) -> Self {
83 Self {
84 x: block.x,
85 z: block.z,
86 }
87 }
88}
89
90fn y_heuristic(dy: f32) -> f32 {
91 if dy > 0. {
92 if BARITONE_COMPAT {
93 return *JUMP_ONE_BLOCK_COST * dy;
95 }
96
97 (f32::max(*JUMP_ONE_BLOCK_COST, WALK_ONE_BLOCK_COST) + JUMP_PENALTY - SPRINT_ONE_BLOCK_COST)
101 * dy
102 } else if dy < 0. {
103 (FALL_N_BLOCKS_COST[2] / 2.) * -dy
106 } else {
107 0.
108 }
109}
110
111#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
113#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
114pub struct YGoal {
115 pub y: i32,
116}
117impl Goal for YGoal {
118 fn heuristic(&self, n: BlockPos) -> f32 {
119 let dy = (self.y - n.y) as f32;
120 y_heuristic(dy)
121 }
122 fn success(&self, n: BlockPos) -> bool {
123 n.y == self.y
124 }
125}
126impl From<BlockPos> for YGoal {
127 fn from(block: BlockPos) -> Self {
128 Self { y: block.y }
129 }
130}
131
132#[derive(Clone, Copy, Debug, Default, PartialEq)]
134#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
135pub struct RadiusGoal {
136 pub pos: Vec3,
137 pub radius: f32,
138}
139impl RadiusGoal {
140 pub fn new(pos: Vec3, radius: f32) -> Self {
141 Self { pos, radius }
142 }
143}
144impl Goal for RadiusGoal {
145 fn heuristic(&self, n: BlockPos) -> f32 {
146 let n = n.center();
147 let dx = (self.pos.x - n.x) as f32;
148 let dy = (self.pos.y - n.y) as f32;
149 let dz = (self.pos.z - n.z) as f32;
150
151 xz_heuristic(dx, dz) + y_heuristic(dy)
152 }
153 fn success(&self, n: BlockPos) -> bool {
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 dx.powi(2) + dy.powi(2) + dz.powi(2) <= self.radius.powi(2)
159 }
160}
161
162#[derive(Debug)]
164#[deprecated = "`InverseGoal` has poor performance and often doesn't work as expected, consider using different goals."]
165pub struct InverseGoal<T: Goal>(pub T);
166#[allow(deprecated)]
167impl<T: Goal> Goal for InverseGoal<T> {
168 fn heuristic(&self, n: BlockPos) -> f32 {
169 -self.0.heuristic(n)
170 }
171 fn success(&self, n: BlockPos) -> bool {
172 !self.0.success(n)
173 }
174}
175
176#[derive(Debug)]
178pub struct OrGoal<T: Goal, U: Goal>(pub T, pub U);
179impl<T: Goal, U: Goal> Goal for OrGoal<T, U> {
180 fn heuristic(&self, n: BlockPos) -> f32 {
181 self.0.heuristic(n).min(self.1.heuristic(n))
182 }
183 fn success(&self, n: BlockPos) -> bool {
184 self.0.success(n) || self.1.success(n)
185 }
186}
187
188#[derive(Debug)]
190pub struct OrGoals<T: Goal>(pub Vec<T>);
191impl<T: Goal> Goal for OrGoals<T> {
192 fn heuristic(&self, n: BlockPos) -> f32 {
193 self.0
194 .iter()
195 .map(|goal| goal.heuristic(n))
196 .min_by(|a, b| a.partial_cmp(b).unwrap())
197 .unwrap_or(f32::INFINITY)
198 }
199 fn success(&self, n: BlockPos) -> bool {
200 self.0.iter().any(|goal| goal.success(n))
201 }
202}
203
204#[derive(Debug)]
206pub struct AndGoal<T: Goal, U: Goal>(pub T, pub U);
207impl<T: Goal, U: Goal> Goal for AndGoal<T, U> {
208 fn heuristic(&self, n: BlockPos) -> f32 {
209 self.0.heuristic(n).max(self.1.heuristic(n))
210 }
211 fn success(&self, n: BlockPos) -> bool {
212 self.0.success(n) && self.1.success(n)
213 }
214}
215
216#[derive(Debug)]
218pub struct AndGoals<T: Goal>(pub Vec<T>);
219impl<T: Goal> Goal for AndGoals<T> {
220 fn heuristic(&self, n: BlockPos) -> f32 {
221 self.0
222 .iter()
223 .map(|goal| goal.heuristic(n))
224 .max_by(|a, b| a.partial_cmp(b).unwrap())
225 .unwrap_or(f32::INFINITY)
226 }
227 fn success(&self, n: BlockPos) -> bool {
228 self.0.iter().all(|goal| goal.success(n))
229 }
230}
231
232#[derive(Clone)]
234pub struct ReachBlockPosGoal {
235 pub pos: BlockPos,
236 pub distance: f64,
237 pub chunk_storage: ChunkStorage,
238
239 max_check_distance: i32,
240}
241impl ReachBlockPosGoal {
242 pub fn new(pos: BlockPos, chunk_storage: ChunkStorage) -> Self {
243 Self::new_with_distance(pos, 4.5, chunk_storage)
244 }
245
246 pub fn new_with_distance(pos: BlockPos, distance: f64, chunk_storage: ChunkStorage) -> Self {
247 Self {
248 pos,
249 distance,
250 chunk_storage,
251 max_check_distance: (distance + 2.).ceil() as i32,
252 }
253 }
254}
255impl Goal for ReachBlockPosGoal {
256 fn heuristic(&self, n: BlockPos) -> f32 {
257 BlockPosGoal(self.pos).heuristic(n)
258 }
259 fn success(&self, n: BlockPos) -> bool {
260 let head = n.up(1);
261 if head == self.pos
264 || head.north(1) == self.pos
265 || head.south(1) == self.pos
266 || head.east(1) == self.pos
267 || head.west(1) == self.pos
268 || head.up(1) == self.pos
269 || head.down(1) == self.pos
270 {
271 return true;
272 }
273
274 let distance_squared = self.pos.distance_squared_to(n);
276 if distance_squared > self.max_check_distance.pow(2) {
277 return false;
278 }
279
280 let eye_position = n.center_bottom().up(1.62);
281 let look_direction = crate::bot::direction_looking_at(eye_position, self.pos.center());
282 let block_hit_result = azalea_client::interact::pick::pick_block(
283 look_direction,
284 eye_position,
285 &self.chunk_storage,
286 self.distance,
287 );
288
289 block_hit_result.block_pos == self.pos
290 }
291}
292impl Debug for ReachBlockPosGoal {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 f.debug_struct("ReachBlockPosGoal")
295 .field("pos", &self.pos)
296 .field("distance", &self.distance)
297 .field("max_check_distance", &self.max_check_distance)
298 .finish()
299 }
300}