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;
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#[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 return *JUMP_ONE_BLOCK_COST * dy;
103 }
104
105 (f32::max(*JUMP_ONE_BLOCK_COST, WALK_ONE_BLOCK_COST) + JUMP_PENALTY - SPRINT_ONE_BLOCK_COST)
109 * dy
110 } else if dy < 0. {
111 (FALL_N_BLOCKS_COST[2] / 2.) * -dy
114 } else {
115 0.
116 }
117}
118
119#[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#[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#[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#[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#[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#[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#[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#[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 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 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}