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