azalea/pathfinder/
goals.rs1use 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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 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}