aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2026-01-15 09:18:28 -0900
committermat <git@matdoes.dev>2026-01-16 04:14:36 +0930
commit914f881dd834aef67755f2e0fd4989253fc9f794 (patch)
tree0075816dff58f5458e48d56bd1874cc8c67114fa
parent9c8cf7adea6da1c76d96f447b71345f63146be10 (diff)
downloadazalea-drasl-914f881dd834aef67755f2e0fd4989253fc9f794.tar.xz
minor pathfinder optimizations and api+doc improvements
-rw-r--r--azalea/examples/testbot/commands/movement.rs2
-rw-r--r--azalea/src/pathfinder/goals.rs32
-rw-r--r--azalea/src/pathfinder/moves/basic.rs12
-rw-r--r--azalea/src/pathfinder/tests.rs4
4 files changed, 36 insertions, 14 deletions
diff --git a/azalea/examples/testbot/commands/movement.rs b/azalea/examples/testbot/commands/movement.rs
index 209a1c80..06c7ebd1 100644
--- a/azalea/examples/testbot/commands/movement.rs
+++ b/azalea/examples/testbot/commands/movement.rs
@@ -26,7 +26,7 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
source.reply("ok");
source
.bot
- .start_goto(BlockPosGoal(BlockPos::from(position)));
+ .start_goto(BlockPosGoal(BlockPos::from(position.up(0.5))));
1
})
.then(literal("xz").then(argument("x", integer()).then(
diff --git a/azalea/src/pathfinder/goals.rs b/azalea/src/pathfinder/goals.rs
index dc933936..0760b5ac 100644
--- a/azalea/src/pathfinder/goals.rs
+++ b/azalea/src/pathfinder/goals.rs
@@ -21,6 +21,17 @@ pub trait Goal: Debug + Send + Sync {
}
/// Move to the given block position.
+///
+/// If you're converting to a `BlockPosGoal` from a [`Vec3`], you should move
+/// the Y up by 0.5 to avoid the pathfinding destroying the final block if it's
+/// non-full like farmland.
+///
+/// ```
+/// # use azalea::{prelude::*, pathfinder::goals::BlockPosGoal};
+/// # fn example(bot: &Client, entity_position: azalea::Vec3) {
+/// bot.start_goto(BlockPosGoal(entity_position.up(0.5).into()));
+/// # }
+/// ```
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct BlockPosGoal(pub BlockPos);
@@ -33,9 +44,7 @@ impl Goal for BlockPosGoal {
xz_heuristic(dx, dz) + y_heuristic(dy)
}
fn success(&self, n: BlockPos) -> bool {
- // the second half of this condition is intended to fix issues when pathing to
- // non-full blocks
- n == self.0 || n.down(1) == self.0
+ n == self.0
}
}
@@ -74,9 +83,19 @@ impl Goal for XZGoal {
n.x == self.x && n.z == self.z
}
}
+impl From<BlockPos> for XZGoal {
+ fn from(block: BlockPos) -> Self {
+ Self {
+ x: block.x,
+ z: block.z,
+ }
+ }
+}
fn y_heuristic(dy: f32) -> f32 {
- if dy > 0.0 {
+ if dy == 0. {
+ 0.
+ } else if dy > 0.0 {
(*JUMP_ONE_BLOCK_COST + JUMP_PENALTY) * dy
} else {
// this assumes that we always descend 2 blocks at a time, which is fine because
@@ -100,6 +119,11 @@ impl Goal for YGoal {
n.y == self.y
}
}
+impl From<BlockPos> for YGoal {
+ fn from(block: BlockPos) -> Self {
+ Self { y: block.y }
+ }
+}
/// Get within the given radius of the given position.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
diff --git a/azalea/src/pathfinder/moves/basic.rs b/azalea/src/pathfinder/moves/basic.rs
index 2814641c..b0df32e4 100644
--- a/azalea/src/pathfinder/moves/basic.rs
+++ b/azalea/src/pathfinder/moves/basic.rs
@@ -94,6 +94,9 @@ fn ascend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
return;
}
+ let base_cost =
+ f32::max(WALK_ONE_BLOCK_COST, *JUMP_ONE_BLOCK_COST) + JUMP_PENALTY + break_cost_1;
+
for dir in CardinalDirection::iter() {
if let Some(stair_facing) = stair_facing {
let expected_stair_facing = cardinal_direction_to_facing_property(dir);
@@ -109,10 +112,7 @@ fn ascend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
continue;
}
- let cost = f32::max(WALK_ONE_BLOCK_COST, *JUMP_ONE_BLOCK_COST)
- + JUMP_PENALTY
- + break_cost_1
- + break_cost_2;
+ let cost = base_cost + break_cost_2;
ctx.edges.push(Edge {
movement: astar::Movement {
@@ -271,8 +271,8 @@ fn descend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
FALL_N_BLOCKS_COST
.get(fall_distance as usize)
.copied()
- // avoid panicking if we fall more than the size of FALL_N_BLOCKS_COST
- // probably not possible but just in case
+ // this isn't possible because we already checked bounds on the fall distance,
+ // but it might be faster to default?
.unwrap_or(f32::INFINITY),
CENTER_AFTER_FALL_COST,
)
diff --git a/azalea/src/pathfinder/tests.rs b/azalea/src/pathfinder/tests.rs
index a630a5b1..a428b177 100644
--- a/azalea/src/pathfinder/tests.rs
+++ b/azalea/src/pathfinder/tests.rs
@@ -281,8 +281,6 @@ fn test_mine_through_non_colliding_block() {
let mut simulation = setup_simulation_world(
&mut partial_chunks,
- // the pathfinder can't actually dig straight down, so we start a block to the side so
- // it can descend correctly
BlockPos::new(0, 72, 1),
&[BlockPos::new(0, 71, 1)],
&[
@@ -296,7 +294,7 @@ fn test_mine_through_non_colliding_block() {
simulation.app.world_mut().write_message(GotoEvent {
entity: simulation.entity,
- goal: Arc::new(BlockPosGoal(BlockPos::new(0, 69, 0))),
+ goal: Arc::new(BlockPosGoal(BlockPos::new(0, 70, 0))),
opts: PathfinderOpts::new()
.min_timeout(PathfinderTimeout::Nodes(1_000_000))
.max_timeout(PathfinderTimeout::Nodes(5_000_000)),