aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--azalea/examples/testbot/commands/movement.rs7
-rw-r--r--azalea/src/pathfinder/astar.rs2
-rw-r--r--azalea/src/pathfinder/costs.rs25
-rw-r--r--azalea/src/pathfinder/goals.rs27
-rw-r--r--azalea/src/pathfinder/moves/basic.rs64
-rw-r--r--azalea/src/pathfinder/moves/mod.rs6
-rw-r--r--azalea/src/pathfinder/moves/parkour.rs3
7 files changed, 107 insertions, 27 deletions
diff --git a/azalea/examples/testbot/commands/movement.rs b/azalea/examples/testbot/commands/movement.rs
index 06c7ebd1..3f015f2c 100644
--- a/azalea/examples/testbot/commands/movement.rs
+++ b/azalea/examples/testbot/commands/movement.rs
@@ -210,4 +210,11 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
*source.state.task.lock() = BotTask::None;
1
}));
+ commands.register(literal("forcestop").executes(|ctx: &Ctx| {
+ let source = ctx.source.lock();
+ source.bot.force_stop_pathfinding();
+ source.reply("ok");
+ *source.state.task.lock() = BotTask::None;
+ 1
+ }));
}
diff --git a/azalea/src/pathfinder/astar.rs b/azalea/src/pathfinder/astar.rs
index ea2d45e2..8eecd016 100644
--- a/azalea/src/pathfinder/astar.rs
+++ b/azalea/src/pathfinder/astar.rs
@@ -326,7 +326,7 @@ impl PathfinderHeap {
}
}
-#[derive(PartialEq)]
+#[derive(PartialEq, Debug)]
#[repr(C)]
pub struct WeightedNode {
/// Sum of the g_score and heuristic
diff --git a/azalea/src/pathfinder/costs.rs b/azalea/src/pathfinder/costs.rs
index d5141294..461f0b5c 100644
--- a/azalea/src/pathfinder/costs.rs
+++ b/azalea/src/pathfinder/costs.rs
@@ -5,8 +5,8 @@ use num_traits::Float;
// based on https://github.com/cabaletta/baritone/blob/1.20.1/src/api/java/baritone/api/pathing/movement/ActionCosts.java
pub const WALK_ONE_BLOCK_COST: f32 = 20. / 4.317; // 4.633
pub const SPRINT_ONE_BLOCK_COST: f32 = 20. / 5.612; // 3.564
-pub const WALK_OFF_BLOCK_COST: f32 = WALK_ONE_BLOCK_COST * 0.8;
-pub const SPRINT_MULTIPLIER: f32 = SPRINT_ONE_BLOCK_COST / WALK_ONE_BLOCK_COST;
+pub const WALK_OFF_BLOCK_COST: f32 = WALK_ONE_BLOCK_COST * 0.8; // 3.706
+pub const SPRINT_MULTIPLIER: f32 = SPRINT_ONE_BLOCK_COST / WALK_ONE_BLOCK_COST; // 0.769
pub const JUMP_PENALTY: f32 = 2.;
pub const CENTER_AFTER_FALL_COST: f32 = WALK_ONE_BLOCK_COST - WALK_OFF_BLOCK_COST; // 0.927
pub const WALK_ONE_IN_WATER_COST: f32 = 20. / 1.960; // 10.204
@@ -26,6 +26,7 @@ pub static FALL_0_25_BLOCKS_COST: LazyLock<f32> = LazyLock::new(|| distance_to_t
pub static JUMP_ONE_BLOCK_COST: LazyLock<f32> =
LazyLock::new(|| *FALL_1_25_BLOCKS_COST - *FALL_0_25_BLOCKS_COST); // 3.163
+// [0, 5.614727, 7.7880826, 9.468678, ..]
pub static FALL_N_BLOCKS_COST: LazyLock<[f32; 4097]> = LazyLock::new(|| {
// mostly the same as calculating distance_to_ticks for every distance, but in
// linear time complexity
@@ -59,8 +60,8 @@ pub static FALL_N_BLOCKS_COST: LazyLock<[f32; 4097]> = LazyLock::new(|| {
fall_n_blocks_cost
});
-fn velocity(ticks: usize) -> f32 {
- (0.98.powi(ticks.try_into().unwrap()) - 1.) * -3.92
+fn velocity(ticks: u32) -> f32 {
+ (0.98.powi(ticks as i32) - 1.) * -3.92
}
fn distance_to_ticks(distance: f32) -> f32 {
@@ -72,9 +73,25 @@ fn distance_to_ticks(distance: f32) -> f32 {
loop {
let fall_distance_per_tick = velocity(tick_count);
if fall_distance_per_tick >= remaining_distance {
+ // add a bit extra to prefer smaller falls even if they're the same number of
+ // ticks
return (tick_count as f32) + (remaining_distance / fall_distance_per_tick);
}
remaining_distance -= fall_distance_per_tick;
tick_count += 1;
}
}
+
+#[cfg(test)]
+mod tests {
+ use crate::pathfinder::costs::{FALL_N_BLOCKS_COST, distance_to_ticks};
+
+ #[test]
+ fn test_fall_n_blocks_cost() {
+ for i in 0..4 {
+ let a = FALL_N_BLOCKS_COST[i];
+ let b = distance_to_ticks(i as f32);
+ assert!((a - b).abs() < 0.1, "{a} != {b}");
+ }
+ }
+}
diff --git a/azalea/src/pathfinder/goals.rs b/azalea/src/pathfinder/goals.rs
index 0760b5ac..26915b2f 100644
--- a/azalea/src/pathfinder/goals.rs
+++ b/azalea/src/pathfinder/goals.rs
@@ -11,7 +11,10 @@ use azalea_world::ChunkStorage;
use serde::{Deserialize, Serialize};
use super::costs::{COST_HEURISTIC, FALL_N_BLOCKS_COST, JUMP_ONE_BLOCK_COST};
-use crate::pathfinder::costs::JUMP_PENALTY;
+use crate::pathfinder::{
+ costs::{JUMP_PENALTY, SPRINT_ONE_BLOCK_COST, WALK_ONE_BLOCK_COST},
+ moves::BARITONE_COMPAT,
+};
pub trait Goal: Debug + Send + Sync {
#[must_use]
@@ -93,14 +96,22 @@ impl From<BlockPos> for XZGoal {
}
fn y_heuristic(dy: f32) -> f32 {
- if dy == 0. {
- 0.
- } else if dy > 0.0 {
- (*JUMP_ONE_BLOCK_COST + JUMP_PENALTY) * dy
+ if dy > 0. {
+ if BARITONE_COMPAT {
+ return *JUMP_ONE_BLOCK_COST * dy;
+ }
+
+ // forward+up is the main way to ascend, so make sure to match that. we subtract
+ // SPRINT_ONE_BLOCK_COST to cancel out the value that should've been
+ // added by the xz heuristic.
+ (f32::max(*JUMP_ONE_BLOCK_COST, WALK_ONE_BLOCK_COST) + JUMP_PENALTY - SPRINT_ONE_BLOCK_COST)
+ * dy
+ } else if dy < 0. {
+ // this is an overestimate (copied from baritone), but fixing it makes perf
+ // worse
+ (FALL_N_BLOCKS_COST[2] / 2.) * -dy
} else {
- // this assumes that we always descend 2 blocks at a time, which is fine because
- // the heuristic should be an underestimate.
- FALL_N_BLOCKS_COST[2] / 2. * -dy
+ 0.
}
}
diff --git a/azalea/src/pathfinder/moves/basic.rs b/azalea/src/pathfinder/moves/basic.rs
index b0df32e4..8739c920 100644
--- a/azalea/src/pathfinder/moves/basic.rs
+++ b/azalea/src/pathfinder/moves/basic.rs
@@ -8,7 +8,9 @@ use azalea_core::{
};
use super::{Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx, default_is_reached};
-use crate::pathfinder::{astar, costs::*, player_pos_to_block_pos, rel_block_pos::RelBlockPos};
+use crate::pathfinder::{
+ astar, costs::*, moves::BARITONE_COMPAT, player_pos_to_block_pos, rel_block_pos::RelBlockPos,
+};
pub fn basic_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
forward_move(ctx, node);
@@ -23,8 +25,13 @@ fn forward_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
let mut base_cost = SPRINT_ONE_BLOCK_COST;
// it's for us cheaper to have the water cost be applied when leaving the water
// rather than when entering
- if ctx.world.is_block_water(pos.down(1)) {
- base_cost = WALK_ONE_IN_WATER_COST;
+ let currently_in_water = ctx.world.is_block_water(pos.down(1));
+ if currently_in_water {
+ if BARITONE_COMPAT {
+ base_cost = WALK_ONE_BLOCK_COST;
+ } else {
+ base_cost = WALK_ONE_IN_WATER_COST;
+ }
}
for dir in CardinalDirection::iter() {
@@ -38,6 +45,15 @@ fn forward_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
}
cost += break_cost;
+ if BARITONE_COMPAT && currently_in_water {
+ let dest_in_water = ctx.world.is_block_water((pos + offset).down(1));
+ if !dest_in_water {
+ // baritone does a descend when we enter water, doesn't matter much in practice
+ // though
+ cost += 2. + FALL_N_BLOCKS_COST[1] + WALK_OFF_BLOCK_COST - SPRINT_ONE_BLOCK_COST;
+ }
+ }
+
ctx.edges.push(Edge {
movement: astar::Movement {
target: pos + offset,
@@ -266,14 +282,15 @@ fn descend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
break_cost_2 = 0.;
}
+ if BARITONE_COMPAT && fall_distance > 1 {
+ fall_distance += 1;
+ }
+
let cost = WALK_OFF_BLOCK_COST
+ f32::max(
- FALL_N_BLOCKS_COST
+ *FALL_N_BLOCKS_COST
.get(fall_distance as usize)
- .copied()
- // this isn't possible because we already checked bounds on the fall distance,
- // but it might be faster to default?
- .unwrap_or(f32::INFINITY),
+ .expect("already checked bounds on fall distance"),
CENTER_AFTER_FALL_COST,
)
+ break_cost_1
@@ -363,6 +380,10 @@ pub fn descend_is_reached(
}
fn descend_forward_1_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
+ if BARITONE_COMPAT {
+ return;
+ }
+
for dir in CardinalDirection::iter() {
let dir_delta = RelBlockPos::new(dir.x(), 0, dir.z());
let gap_horizontal_position = pos + dir_delta;
@@ -416,8 +437,14 @@ fn descend_forward_1_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
fn diagonal_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
let mut base_cost = SPRINT_ONE_BLOCK_COST;
- if ctx.world.is_block_water(pos.down(1)) {
- base_cost = WALK_ONE_IN_WATER_COST;
+
+ let currently_in_water = ctx.world.is_block_water(pos.down(1));
+ if currently_in_water {
+ if BARITONE_COMPAT {
+ base_cost = WALK_ONE_BLOCK_COST;
+ } else {
+ base_cost = WALK_ONE_IN_WATER_COST;
+ }
}
// add 0.001 as a tie-breaker to avoid unnecessarily going diagonal
@@ -439,14 +466,27 @@ fn diagonal_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
}
if !left_passable || !right_passable {
- // add a bit of cost because it'll probably be hugging a wall here
- cost += WALK_ONE_BLOCK_COST / 2.;
+ if !BARITONE_COMPAT {
+ // add a bit of cost because it'll probably be hugging a wall here
+ cost += WALK_ONE_BLOCK_COST / 2.;
+ } else {
+ cost = WALK_ONE_BLOCK_COST * (SQRT_2 - 0.001) * SQRT_2;
+ }
}
if !ctx.world.is_standable(pos + offset) {
continue;
}
+ if BARITONE_COMPAT && currently_in_water {
+ let dest_in_water = ctx.world.is_block_water((pos + offset).down(1));
+ if !dest_in_water {
+ // baritone does a descend when we enter water, doesn't matter much in practice
+ // though
+ cost += 2. + FALL_N_BLOCKS_COST[1] + WALK_OFF_BLOCK_COST - SPRINT_ONE_BLOCK_COST;
+ }
+ }
+
ctx.edges.push(Edge {
movement: astar::Movement {
target: pos + offset,
diff --git a/azalea/src/pathfinder/moves/mod.rs b/azalea/src/pathfinder/moves/mod.rs
index d575c01a..3d7f0ade 100644
--- a/azalea/src/pathfinder/moves/mod.rs
+++ b/azalea/src/pathfinder/moves/mod.rs
@@ -35,6 +35,12 @@ type Edge = astar::Edge<RelBlockPos, MoveData>;
pub type SuccessorsFn = fn(&mut PathfinderCtx, RelBlockPos);
+/// Re-implement certain bugs and quirks that Baritone has, and disable
+/// movements that Baritone doesn't have.
+///
+/// Meant to help with debugging when directly comparing against Baritone.
+pub const BARITONE_COMPAT: bool = false;
+
pub fn default_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
basic::basic_move(ctx, node);
parkour::parkour_move(ctx, node);
diff --git a/azalea/src/pathfinder/moves/parkour.rs b/azalea/src/pathfinder/moves/parkour.rs
index 9ff10417..bb92a137 100644
--- a/azalea/src/pathfinder/moves/parkour.rs
+++ b/azalea/src/pathfinder/moves/parkour.rs
@@ -52,8 +52,7 @@ fn parkour_forward_1_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
if !ctx.world.is_block_passable((pos + offset).up(2)) {
continue;
}
-
- let cost = JUMP_PENALTY + WALK_ONE_BLOCK_COST * 2. + CENTER_AFTER_FALL_COST;
+ let cost = JUMP_PENALTY + WALK_ONE_BLOCK_COST * 2.;
ctx.edges.push(Edge {
movement: astar::Movement {