1use std::f32::consts::SQRT_2;
2
3use azalea_block::{BlockState, properties};
4use azalea_client::{SprintDirection, WalkDirection};
5use azalea_core::{
6 direction::CardinalDirection,
7 position::{BlockPos, Vec3},
8};
9
10use super::{Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx, default_is_reached};
11use crate::pathfinder::{astar, costs::*, rel_block_pos::RelBlockPos};
12
13pub fn basic_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
14 forward_move(ctx, node);
15 ascend_move(ctx, node);
16 descend_move(ctx, node);
17 diagonal_move(ctx, node);
18 descend_forward_1_move(ctx, node);
19 downward_move(ctx, node);
20}
21
22fn forward_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
23 for dir in CardinalDirection::iter() {
24 let offset = RelBlockPos::new(dir.x(), 0, dir.z());
25
26 let mut cost = SPRINT_ONE_BLOCK_COST;
27
28 let break_cost = ctx.world.cost_for_standing(pos + offset, ctx.mining_cache);
29 if break_cost == f32::INFINITY {
30 continue;
31 }
32 cost += break_cost;
33
34 ctx.edges.push(Edge {
35 movement: astar::Movement {
36 target: pos + offset,
37 data: MoveData {
38 execute: &execute_forward_move,
39 is_reached: &default_is_reached,
40 },
41 },
42 cost,
43 })
44 }
45}
46
47fn execute_forward_move(mut ctx: ExecuteCtx) {
48 let center = ctx.target.center();
49
50 if ctx.mine_while_at_start(ctx.target.up(1)) {
51 return;
52 }
53 if ctx.mine_while_at_start(ctx.target) {
54 return;
55 }
56
57 ctx.look_at(center);
58 ctx.sprint(SprintDirection::Forward);
59}
60
61fn ascend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
62 let is_unusual_shape = !ctx.world.is_block_solid(pos.down(1));
66 let mut stair_facing = None;
67
68 if is_unusual_shape {
69 let block_below = ctx.world.get_block_state(pos.down(1));
72
73 let Some(found_stair_facing) = validate_stair_and_get_facing(block_below) else {
74 return;
77 };
78
79 stair_facing = Some(found_stair_facing);
80 }
81
82 for dir in CardinalDirection::iter() {
83 if let Some(stair_facing) = stair_facing {
84 let expected_stair_facing = cardinal_direction_to_facing_property(dir);
85 if stair_facing != expected_stair_facing {
86 continue;
87 }
88 }
89
90 let offset = RelBlockPos::new(dir.x(), 1, dir.z());
91
92 let break_cost_1 = ctx
93 .world
94 .cost_for_breaking_block(pos.up(2), ctx.mining_cache);
95 if break_cost_1 == f32::INFINITY {
96 continue;
97 }
98 let break_cost_2 = ctx.world.cost_for_standing(pos + offset, ctx.mining_cache);
99 if break_cost_2 == f32::INFINITY {
100 continue;
101 }
102
103 let cost = SPRINT_ONE_BLOCK_COST
104 + JUMP_PENALTY
105 + *JUMP_ONE_BLOCK_COST
106 + break_cost_1
107 + break_cost_2;
108
109 ctx.edges.push(Edge {
110 movement: astar::Movement {
111 target: pos + offset,
112 data: MoveData {
113 execute: &execute_ascend_move,
114 is_reached: &ascend_is_reached,
115 },
116 },
117 cost,
118 })
119 }
120}
121fn execute_ascend_move(mut ctx: ExecuteCtx) {
122 let ExecuteCtx {
123 target,
124 start,
125 position,
126 physics,
127 ..
128 } = ctx;
129
130 if ctx.mine_while_at_start(start.up(2)) {
131 return;
132 }
133 if ctx.mine_while_at_start(target) {
134 return;
135 }
136 if ctx.mine_while_at_start(target.up(1)) {
137 return;
138 }
139
140 let target_center = target.center();
141
142 ctx.look_at(target_center);
143 ctx.walk(WalkDirection::Forward);
144
145 let x_axis = (start.x - target.x).abs(); let z_axis = (start.z - target.z).abs(); let flat_distance_to_next = x_axis as f64 * (target_center.x - position.x)
152 + z_axis as f64 * (target_center.z - position.z);
153 let side_distance = z_axis as f64 * (target_center.x - position.x).abs()
154 + x_axis as f64 * (target_center.z - position.z).abs();
155
156 let lateral_motion = x_axis as f64 * physics.velocity.z + z_axis as f64 * physics.velocity.x;
157 if lateral_motion.abs() > 0.1 {
158 return;
159 }
160
161 if flat_distance_to_next > 1.2 || side_distance > 0.2 {
162 return;
163 }
164
165 let block_below_target = ctx.get_block_state(target.down(1));
168 if let Some(stair_facing) = validate_stair_and_get_facing(block_below_target) {
169 let expected_stair_facing = match (x_axis, z_axis) {
170 (0, 1) => Some(properties::FacingCardinal::North),
171 (1, 0) => Some(properties::FacingCardinal::East),
172 (0, -1) => Some(properties::FacingCardinal::South),
173 (-1, 0) => Some(properties::FacingCardinal::West),
174 _ => None,
175 };
176 if let Some(expected_stair_facing) = expected_stair_facing
177 && stair_facing == expected_stair_facing
178 {
179 return;
180 }
181 }
182
183 if BlockPos::from(position) == start {
184 if target.y as f64 - position.y > 0.5 {
186 ctx.jump();
187 }
188 }
189}
190#[must_use]
191pub fn ascend_is_reached(
192 IsReachedCtx {
193 position, target, ..
194 }: IsReachedCtx,
195) -> bool {
196 BlockPos::from(position) == target || BlockPos::from(position) == target.down(1)
197}
198
199fn validate_stair_and_get_facing(block_state: BlockState) -> Option<properties::FacingCardinal> {
200 let top_bottom = block_state.property::<properties::TopBottom>();
201 if top_bottom != Some(properties::TopBottom::Bottom) {
202 return None;
203 }
204
205 block_state.property::<properties::FacingCardinal>()
206}
207fn cardinal_direction_to_facing_property(dir: CardinalDirection) -> properties::FacingCardinal {
208 match dir {
209 CardinalDirection::North => properties::FacingCardinal::North,
210 CardinalDirection::East => properties::FacingCardinal::East,
211 CardinalDirection::South => properties::FacingCardinal::South,
212 CardinalDirection::West => properties::FacingCardinal::West,
213 }
214}
215
216fn descend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
217 for dir in CardinalDirection::iter() {
218 let dir_delta = RelBlockPos::new(dir.x(), 0, dir.z());
219 let new_horizontal_position = pos + dir_delta;
220
221 let break_cost_1 = ctx
222 .world
223 .cost_for_passing(new_horizontal_position, ctx.mining_cache);
224 if break_cost_1 == f32::INFINITY {
225 continue;
226 }
227
228 let mut fall_distance = ctx.world.fall_distance(new_horizontal_position);
229 if fall_distance > 3 {
230 continue;
231 }
232
233 if fall_distance == 0 {
234 fall_distance = 1
236 }
237
238 let new_position = new_horizontal_position.down(fall_distance as i32);
239
240 let break_cost_2;
242 if fall_distance == 1 {
243 break_cost_2 = ctx.world.cost_for_standing(new_position, ctx.mining_cache);
244 if break_cost_2 == f32::INFINITY {
245 continue;
246 }
247 } else {
248 if !ctx.world.is_standable(new_position) {
250 continue;
251 }
252 break_cost_2 = 0.;
253 }
254
255 let cost = WALK_OFF_BLOCK_COST
256 + f32::max(
257 FALL_N_BLOCKS_COST
258 .get(fall_distance as usize)
259 .copied()
260 .unwrap_or(f32::INFINITY),
263 CENTER_AFTER_FALL_COST,
264 )
265 + break_cost_1
266 + break_cost_2;
267
268 ctx.edges.push(Edge {
269 movement: astar::Movement {
270 target: new_position,
271 data: MoveData {
272 execute: &execute_descend_move,
273 is_reached: &descend_is_reached,
274 },
275 },
276 cost,
277 })
278 }
279}
280fn execute_descend_move(mut ctx: ExecuteCtx) {
281 let ExecuteCtx {
282 target,
283 start,
284 position,
285 ..
286 } = ctx;
287
288 for i in (0..=(start.y - target.y + 1)).rev() {
289 if ctx.mine_while_at_start(target.up(i)) {
290 return;
291 }
292 }
293
294 let start_center = start.center();
295 let center = target.center();
296
297 let horizontal_distance_from_target = (center - position).horizontal_distance_squared().sqrt();
298 let horizontal_distance_from_start = (start.center() - position)
299 .horizontal_distance_squared()
300 .sqrt();
301
302 let dest_ahead = Vec3::new(
303 start_center.x + (center.x - start_center.x) * 1.5,
304 center.y,
305 start_center.z + (center.z - start_center.z) * 1.5,
306 );
307
308 if BlockPos::from(position) != target || horizontal_distance_from_target > 0.25 {
309 if horizontal_distance_from_start < 1.25 {
310 ctx.look_at(dest_ahead);
312 ctx.walk(WalkDirection::Forward);
313 } else {
314 ctx.look_at(center);
315 ctx.walk(WalkDirection::Forward);
316 }
317 } else {
318 ctx.walk(WalkDirection::None);
319 }
320}
321#[must_use]
322pub fn descend_is_reached(
323 IsReachedCtx {
324 target,
325 start,
326 position,
327 physics,
328 ..
329 }: IsReachedCtx,
330) -> bool {
331 let dest_ahead = BlockPos::new(
332 start.x + (target.x - start.x) * 2,
333 target.y,
334 start.z + (target.z - start.z) * 2,
335 );
336
337 if BlockPos::from(position) == target || BlockPos::from(position) == dest_ahead {
338 if (position.y - target.y as f64) < 0.5 {
339 return true;
340 }
341 } else if BlockPos::from(position).up(1) == target && physics.on_ground() {
342 return true;
343 }
344 false
345}
346
347fn descend_forward_1_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
348 for dir in CardinalDirection::iter() {
349 let dir_delta = RelBlockPos::new(dir.x(), 0, dir.z());
350 let gap_horizontal_position = pos + dir_delta;
351 let new_horizontal_position = pos + dir_delta * 2;
352
353 let gap_fall_distance = ctx.world.fall_distance(gap_horizontal_position);
354 let fall_distance = ctx.world.fall_distance(new_horizontal_position);
355
356 if fall_distance == 0 || fall_distance > 3 || gap_fall_distance < fall_distance {
357 continue;
358 }
359
360 let new_position = new_horizontal_position.down(fall_distance as i32);
361
362 if !ctx.world.is_passable(new_horizontal_position) {
364 continue;
365 }
366 if !ctx.world.is_passable(gap_horizontal_position) {
367 continue;
368 }
369 if !ctx.world.is_standable(new_position) {
371 continue;
372 }
373
374 let cost = WALK_OFF_BLOCK_COST
375 + WALK_ONE_BLOCK_COST
376 + f32::max(
377 FALL_N_BLOCKS_COST
378 .get(fall_distance as usize)
379 .copied()
380 .unwrap_or(f32::INFINITY),
383 CENTER_AFTER_FALL_COST,
384 );
385
386 ctx.edges.push(Edge {
387 movement: astar::Movement {
388 target: new_position,
389 data: MoveData {
390 execute: &execute_descend_move,
391 is_reached: &descend_is_reached,
392 },
393 },
394 cost,
395 })
396 }
397}
398
399fn diagonal_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
400 for dir in CardinalDirection::iter() {
401 let right = dir.right();
402 let offset = RelBlockPos::new(dir.x() + right.x(), 0, dir.z() + right.z());
403 let left_pos = RelBlockPos::new(pos.x + dir.x(), pos.y, pos.z + dir.z());
404 let right_pos = RelBlockPos::new(pos.x + right.x(), pos.y, pos.z + right.z());
405
406 let mut cost = SPRINT_ONE_BLOCK_COST * SQRT_2 + 0.001;
408
409 let left_passable = ctx.world.is_passable(left_pos);
410 let right_passable = ctx.world.is_passable(right_pos);
411
412 if !left_passable && !right_passable {
413 continue;
414 }
415
416 if !left_passable || !right_passable {
417 cost += WALK_ONE_BLOCK_COST / 2.;
419 }
420
421 if !ctx.world.is_standable(pos + offset) {
422 continue;
423 }
424
425 ctx.edges.push(Edge {
426 movement: astar::Movement {
427 target: pos + offset,
428 data: MoveData {
429 execute: &execute_diagonal_move,
430 is_reached: &default_is_reached,
431 },
432 },
433 cost,
434 })
435 }
436}
437fn execute_diagonal_move(mut ctx: ExecuteCtx) {
438 let target_center = ctx.target.center();
439
440 ctx.look_at(target_center);
441 ctx.sprint(SprintDirection::Forward);
442}
443
444fn downward_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
446 if !ctx.world.is_block_solid(pos.down(2)) {
448 return;
449 }
450
451 let break_cost = ctx
452 .world
453 .cost_for_breaking_block(pos.down(1), ctx.mining_cache);
454 if break_cost == f32::INFINITY {
455 return;
456 }
457
458 let cost = FALL_N_BLOCKS_COST[1] + break_cost;
459
460 ctx.edges.push(Edge {
461 movement: astar::Movement {
462 target: pos.down(1),
463 data: MoveData {
464 execute: &execute_downward_move,
465 is_reached: &default_is_reached,
466 },
467 },
468 cost,
469 })
470}
471fn execute_downward_move(mut ctx: ExecuteCtx) {
472 let ExecuteCtx {
473 target, position, ..
474 } = ctx;
475
476 let target_center = target.center();
477
478 let horizontal_distance_from_target = (target_center - position)
479 .horizontal_distance_squared()
480 .sqrt();
481
482 if horizontal_distance_from_target > 0.25 {
483 ctx.look_at(target_center);
484 ctx.walk(WalkDirection::Forward);
485 } else if ctx.mine_while_at_start(target) {
486 ctx.walk(WalkDirection::None);
487 } else if BlockPos::from(position) != target {
488 ctx.look_at(target_center);
489 ctx.walk(WalkDirection::Forward);
490 } else {
491 ctx.walk(WalkDirection::None);
492 }
493}