diff options
| -rw-r--r-- | azalea/examples/testbot/commands/movement.rs | 7 | ||||
| -rw-r--r-- | azalea/src/pathfinder/astar.rs | 2 | ||||
| -rw-r--r-- | azalea/src/pathfinder/costs.rs | 25 | ||||
| -rw-r--r-- | azalea/src/pathfinder/goals.rs | 27 | ||||
| -rw-r--r-- | azalea/src/pathfinder/moves/basic.rs | 64 | ||||
| -rw-r--r-- | azalea/src/pathfinder/moves/mod.rs | 6 | ||||
| -rw-r--r-- | azalea/src/pathfinder/moves/parkour.rs | 3 |
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 { |
