diff --git a/azalea/src/pathfinder/astar.rs b/azalea/src/pathfinder/astar.rs
index d4812e9b..e5542531 100644
--- a/azalea/src/pathfinder/astar.rs
+++ b/azalea/src/pathfinder/astar.rs
@@ -106,7 +106,7 @@ where
}
}
- let best_path = determine_best_path(&best_paths, &heuristic);
+ let best_path = determine_best_path(&best_paths, &start);
Path {
movements: reconstruct_path(nodes, best_path),
@@ -114,23 +114,20 @@ where
}
}
-const MIN_DISTANCE_PATH: f32 = 5.;
-
-fn determine_best_path
(best_node: &[P; 7], heuristic: &HeuristicFn) -> P
+fn determine_best_path
(best_paths: &[P; 7], start: &P) -> P
where
- HeuristicFn: Fn(P) -> f32,
P: Eq + Hash + Copy + Debug,
{
// this basically makes sure we don't create a path that's really short
- for node in best_node.iter() {
- // square MIN_DISTANCE_PATH because we're comparing squared distances
- if heuristic(*node) > MIN_DISTANCE_PATH * MIN_DISTANCE_PATH {
+ for node in best_paths.iter() {
+ if node != start {
+ println!("chose best node {:?}", node);
return *node;
}
}
warn!("No best node found, returning first node");
- best_node[0]
+ best_paths[0]
}
fn reconstruct_path
(mut nodes: HashMap
>, current: P) -> Vec>
diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs
index 9ecc6093..b8a02798 100644
--- a/azalea/src/pathfinder/mod.rs
+++ b/azalea/src/pathfinder/mod.rs
@@ -86,7 +86,7 @@ pub struct GotoEvent {
pub entity: Entity,
pub goal: Arc,
/// The function that's used for checking what moves are possible. Usually
- /// `pathfinder::moves::basic::basic_move`
+ /// `pathfinder::moves::default_move`
pub successors_fn: SuccessorsFn,
}
#[derive(Event)]
@@ -124,7 +124,7 @@ impl PathfinderClientExt for azalea_client::Client {
self.ecs.lock().send_event(GotoEvent {
entity: self.entity,
goal: Arc::new(goal),
- successors_fn: moves::basic::basic_move,
+ successors_fn: moves::default_move,
});
}
}
@@ -370,7 +370,12 @@ fn tick_execute_path(
position: **position,
physics,
};
- if (movement.data.is_reached)(is_reached_ctx) {
+ let on_ground_if_last = if i == pathfinder.path.len() - 1 {
+ physics.on_ground
+ } else {
+ true
+ };
+ if (movement.data.is_reached)(is_reached_ctx) && on_ground_if_last {
pathfinder.path = pathfinder.path.split_off(i + 1);
pathfinder.last_reached_node = Some(movement.target);
pathfinder.last_node_reached_at = Some(Instant::now());
diff --git a/azalea/src/pathfinder/moves/basic.rs b/azalea/src/pathfinder/moves/basic.rs
index af940dc2..b8cbbbb7 100644
--- a/azalea/src/pathfinder/moves/basic.rs
+++ b/azalea/src/pathfinder/moves/basic.rs
@@ -268,7 +268,8 @@ fn diagonal_move(world: &Instance, pos: BlockPos) -> Vec {
if !is_standable(&(pos + offset), world) {
continue;
}
- let cost = SPRINT_ONE_BLOCK_COST * SQRT_2;
+ // +0.001 so it doesn't unnecessarily go diagonal sometimes
+ let cost = SPRINT_ONE_BLOCK_COST * SQRT_2 + 0.001;
edges.push(Edge {
movement: astar::Movement {
diff --git a/azalea/src/pathfinder/moves/mod.rs b/azalea/src/pathfinder/moves/mod.rs
index 01df0929..61dc8b68 100644
--- a/azalea/src/pathfinder/moves/mod.rs
+++ b/azalea/src/pathfinder/moves/mod.rs
@@ -1,4 +1,5 @@
pub mod basic;
+pub mod parkour;
use std::fmt::Debug;
@@ -114,6 +115,13 @@ pub struct IsReachedCtx<'a> {
pub physics: &'a azalea_entity::Physics,
}
+pub fn default_move(world: &Instance, node: BlockPos) -> Vec {
+ let mut edges = Vec::new();
+ edges.extend(basic::basic_move(world, node));
+ edges.extend(parkour::parkour_move(world, node));
+ edges
+}
+
/// Returns whether the entity is at the node and should start going to the
/// next node.
#[must_use]
diff --git a/azalea/src/pathfinder/moves/parkour.rs b/azalea/src/pathfinder/moves/parkour.rs
new file mode 100644
index 00000000..2a2f132f
--- /dev/null
+++ b/azalea/src/pathfinder/moves/parkour.rs
@@ -0,0 +1,233 @@
+use azalea_client::{SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection};
+use azalea_core::{BlockPos, CardinalDirection};
+use azalea_world::Instance;
+
+use crate::{
+ pathfinder::{astar, costs::*},
+ JumpEvent, LookAtEvent,
+};
+
+use super::{
+ default_is_reached, is_block_passable, is_block_solid, is_passable, is_standable, Edge,
+ ExecuteCtx, MoveData,
+};
+
+pub fn parkour_move(world: &Instance, node: BlockPos) -> Vec {
+ let mut edges = Vec::new();
+ edges.extend(parkour_forward_1_move(world, node));
+ edges.extend(parkour_headhitter_forward_1_move(world, node));
+ edges.extend(parkour_forward_2_move(world, node));
+ edges
+}
+
+fn parkour_forward_1_move(world: &Instance, pos: BlockPos) -> Vec {
+ let mut edges = Vec::new();
+ for dir in CardinalDirection::iter() {
+ let gap_offset = BlockPos::new(dir.x() * 1, 0, dir.z() * 1);
+ let offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2);
+
+ if !is_standable(&(pos + offset), world) {
+ continue;
+ }
+ if !is_passable(&(pos + gap_offset), world) {
+ continue;
+ }
+ if !is_block_passable(&(pos + gap_offset).up(2), world) {
+ continue;
+ }
+ // make sure we actually have to jump
+ if is_block_solid(&(pos + gap_offset).down(1), world) {
+ continue;
+ }
+ // make sure it's not a headhitter
+ if !is_block_passable(&pos.up(2), world) {
+ continue;
+ }
+
+ let cost = *JUMP_ONE_BLOCK_COST + SPRINT_ONE_BLOCK_COST + SPRINT_ONE_BLOCK_COST;
+
+ edges.push(Edge {
+ movement: astar::Movement {
+ target: pos + offset,
+ data: MoveData {
+ execute: &execute_parkour_move,
+ is_reached: &default_is_reached,
+ },
+ },
+ cost,
+ })
+ }
+
+ edges
+}
+
+fn parkour_forward_2_move(world: &Instance, pos: BlockPos) -> Vec {
+ let mut edges = Vec::new();
+ for dir in CardinalDirection::iter() {
+ let gap_1_offset = BlockPos::new(dir.x() * 1, 0, dir.z() * 1);
+ let gap_2_offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2);
+ let offset = BlockPos::new(dir.x() * 3, 0, dir.z() * 3);
+
+ if !is_standable(&(pos + offset), world) {
+ continue;
+ }
+ if !is_passable(&(pos + gap_1_offset), world) {
+ continue;
+ }
+ if !is_block_passable(&(pos + gap_1_offset).up(2), world) {
+ continue;
+ }
+ if !is_passable(&(pos + gap_2_offset), world) {
+ continue;
+ }
+ if !is_block_passable(&(pos + gap_2_offset).up(2), world) {
+ continue;
+ }
+ // make sure we actually have to jump
+ if is_block_solid(&(pos + gap_1_offset).down(1), world) {
+ continue;
+ }
+ // make sure it's not a headhitter
+ if !is_block_passable(&pos.up(2), world) {
+ continue;
+ }
+
+ let cost = *JUMP_ONE_BLOCK_COST
+ + SPRINT_ONE_BLOCK_COST
+ + SPRINT_ONE_BLOCK_COST
+ + SPRINT_ONE_BLOCK_COST;
+
+ edges.push(Edge {
+ movement: astar::Movement {
+ target: pos + offset,
+ data: MoveData {
+ execute: &execute_parkour_move,
+ is_reached: &default_is_reached,
+ },
+ },
+ cost,
+ })
+ }
+
+ edges
+}
+
+fn parkour_headhitter_forward_1_move(world: &Instance, pos: BlockPos) -> Vec {
+ let mut edges = Vec::new();
+ for dir in CardinalDirection::iter() {
+ let gap_offset = BlockPos::new(dir.x() * 1, 0, dir.z() * 1);
+ let offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2);
+
+ if !is_standable(&(pos + offset), world) {
+ continue;
+ }
+ if !is_passable(&(pos + gap_offset), world) {
+ continue;
+ }
+ if !is_block_passable(&(pos + gap_offset).up(2), world) {
+ continue;
+ }
+ // make sure we actually have to jump
+ if is_block_solid(&(pos + gap_offset).down(1), world) {
+ continue;
+ }
+ // make sure it is a headhitter
+ if !is_block_solid(&pos.up(2), world) {
+ continue;
+ }
+
+ let cost = *JUMP_ONE_BLOCK_COST + WALK_ONE_BLOCK_COST + WALK_ONE_BLOCK_COST;
+
+ edges.push(Edge {
+ movement: astar::Movement {
+ target: pos + offset,
+ data: MoveData {
+ execute: &execute_headhitter_parkour_move,
+ is_reached: &default_is_reached,
+ },
+ },
+ cost,
+ })
+ }
+
+ edges
+}
+
+fn execute_parkour_move(
+ ExecuteCtx {
+ entity,
+ target,
+ start,
+ look_at_events,
+ sprint_events,
+ walk_events,
+ jump_events,
+ ..
+ }: ExecuteCtx,
+) {
+ let center = target.center();
+ look_at_events.send(LookAtEvent {
+ entity,
+ position: center,
+ });
+
+ let jump_distance = i32::max((target - start).x.abs(), (target - start).z.abs());
+
+ if jump_distance > 2 {
+ sprint_events.send(StartSprintEvent {
+ entity,
+ direction: SprintDirection::Forward,
+ });
+ } else {
+ walk_events.send(StartWalkEvent {
+ entity,
+ direction: WalkDirection::Forward,
+ });
+ }
+
+ jump_events.send(JumpEvent { entity });
+}
+
+fn execute_headhitter_parkour_move(
+ ExecuteCtx {
+ entity,
+ target,
+ start,
+ position,
+ look_at_events,
+ sprint_events,
+ walk_events,
+ jump_events,
+ ..
+ }: ExecuteCtx,
+) {
+ let center = target.center();
+ look_at_events.send(LookAtEvent {
+ entity,
+ position: center,
+ });
+
+ let jump_distance = i32::max((target - start).x.abs(), (target - start).z.abs());
+
+ if jump_distance > 2 {
+ sprint_events.send(StartSprintEvent {
+ entity,
+ direction: SprintDirection::Forward,
+ });
+ } else {
+ walk_events.send(StartWalkEvent {
+ entity,
+ direction: WalkDirection::Forward,
+ });
+ }
+
+ let start_center = start.center();
+ let distance_from_start = f64::max(
+ (start_center.x as f64 - position.x).abs(),
+ (start_center.z as f64 - position.z).abs(),
+ );
+
+ if distance_from_start > 0.75 {
+ jump_events.send(JumpEvent { entity });
+ }
+}