diff options
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | azalea-client/src/local_player.rs | 2 | ||||
| -rw-r--r-- | azalea-client/src/packet_handling/game.rs | 4 | ||||
| -rw-r--r-- | azalea-core/Cargo.toml | 1 | ||||
| -rwxr-xr-x | azalea-core/src/position.rs | 18 | ||||
| -rwxr-xr-x | azalea-world/src/chunk_storage.rs | 5 | ||||
| -rw-r--r-- | azalea-world/src/lib.rs | 6 | ||||
| -rw-r--r-- | azalea/benches/pathfinder.rs | 6 | ||||
| -rw-r--r-- | azalea/src/pathfinder/astar.rs | 4 | ||||
| -rw-r--r-- | azalea/src/pathfinder/mod.rs | 31 | ||||
| -rw-r--r-- | azalea/src/pathfinder/moves/basic.rs | 52 | ||||
| -rw-r--r-- | azalea/src/pathfinder/moves/mod.rs | 175 | ||||
| -rw-r--r-- | azalea/src/pathfinder/moves/parkour.rs | 54 |
13 files changed, 209 insertions, 150 deletions
@@ -327,6 +327,7 @@ dependencies = [ "azalea-nbt", "azalea-registry", "bevy_ecs", + "nohash-hasher", "num-traits", "serde", "uuid", diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs index 22ea2232..c2b16434 100644 --- a/azalea-client/src/local_player.rs +++ b/azalea-client/src/local_player.rs @@ -127,7 +127,7 @@ impl InstanceHolder { InstanceHolder { instance: world, partial_instance: Arc::new(RwLock::new(PartialInstance::new( - azalea_world::calculate_chunk_storage_range( + azalea_world::chunk_storage::calculate_chunk_storage_range( client_information.view_distance.into(), ), Some(entity), diff --git a/azalea-client/src/packet_handling/game.rs b/azalea-client/src/packet_handling/game.rs index 049eab03..46eb21fe 100644 --- a/azalea-client/src/packet_handling/game.rs +++ b/azalea-client/src/packet_handling/game.rs @@ -267,7 +267,7 @@ pub fn process_packet_events(ecs: &mut World) { // instance_container) *instance_holder.partial_instance.write() = PartialInstance::new( - azalea_world::calculate_chunk_storage_range( + azalea_world::chunk_storage::calculate_chunk_storage_range( client_information.view_distance.into(), ), // this argument makes it so other clients don't update this player entity @@ -1287,7 +1287,7 @@ pub fn process_packet_events(ecs: &mut World) { // (when we add chunks or entities those will be in the // instance_container) *instance_holder.partial_instance.write() = PartialInstance::new( - azalea_world::calculate_chunk_storage_range( + azalea_world::chunk_storage::calculate_chunk_storage_range( client_information.view_distance.into(), ), Some(player_entity), diff --git a/azalea-core/Cargo.toml b/azalea-core/Cargo.toml index a660c1e2..1859c6a8 100644 --- a/azalea-core/Cargo.toml +++ b/azalea-core/Cargo.toml @@ -14,6 +14,7 @@ azalea-inventory = { version = "0.8.0", path = "../azalea-inventory" } azalea-nbt = { path = "../azalea-nbt", version = "0.8.0" } azalea-registry = { path = "../azalea-registry", version = "0.8.0" } bevy_ecs = { version = "0.11.2", default-features = false, optional = true } +nohash-hasher = "0.2.0" num-traits = "0.2.16" serde = { version = "^1.0", optional = true } uuid = "^1.4.1" diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index 09b10563..09ed44fd 100755 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -5,6 +5,7 @@ use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable}; use std::{ + hash::Hash, io::{Cursor, Write}, ops::{Add, AddAssign, Mul, Rem, Sub}, }; @@ -193,7 +194,7 @@ impl BlockPos { /// Chunk coordinates are used to represent where a chunk is in the world. You /// can convert the x and z to block coordinates by multiplying them by 16. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, McBuf)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, McBuf)] pub struct ChunkPos { pub x: i32, pub z: i32, @@ -213,6 +214,16 @@ impl Add<ChunkPos> for ChunkPos { } } } +impl Hash for ChunkPos { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + // optimized hash that only calls hash once + let combined = (self.x as u64) << 32 | (self.z as u64); + combined.hash(state); + } +} +/// nohash_hasher lets us have IntMap<ChunkPos, _> which is significantly faster +/// than a normal HashMap +impl nohash_hasher::IsEnabled for ChunkPos {} /// The coordinates of a chunk section in the world. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] @@ -273,6 +284,7 @@ pub struct GlobalPos { } impl From<&BlockPos> for ChunkPos { + #[inline] fn from(pos: &BlockPos) -> Self { ChunkPos { x: pos.x.div_floor(16), @@ -298,6 +310,7 @@ impl From<ChunkSectionPos> for ChunkPos { } impl From<&BlockPos> for ChunkBlockPos { + #[inline] fn from(pos: &BlockPos) -> Self { ChunkBlockPos { x: pos.x.rem_euclid(16) as u8, @@ -318,6 +331,7 @@ impl From<&BlockPos> for ChunkSectionBlockPos { } impl From<&ChunkBlockPos> for ChunkSectionBlockPos { + #[inline] fn from(pos: &ChunkBlockPos) -> Self { ChunkSectionBlockPos { x: pos.x, @@ -327,6 +341,7 @@ impl From<&ChunkBlockPos> for ChunkSectionBlockPos { } } impl From<&Vec3> for BlockPos { + #[inline] fn from(pos: &Vec3) -> Self { BlockPos { x: pos.x.floor() as i32, @@ -336,6 +351,7 @@ impl From<&Vec3> for BlockPos { } } impl From<Vec3> for BlockPos { + #[inline] fn from(pos: Vec3) -> Self { BlockPos::from(&pos) } diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs index 2e93f2fb..ce611a61 100755 --- a/azalea-world/src/chunk_storage.rs +++ b/azalea-world/src/chunk_storage.rs @@ -7,6 +7,7 @@ use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; use azalea_nbt::NbtCompound; use log::{debug, trace, warn}; +use nohash_hasher::IntMap; use parking_lot::RwLock; use std::str::FromStr; use std::{ @@ -37,7 +38,7 @@ pub struct PartialChunkStorage { pub struct ChunkStorage { pub height: u32, pub min_y: i32, - pub map: HashMap<ChunkPos, Weak<RwLock<Chunk>>>, + pub map: IntMap<ChunkPos, Weak<RwLock<Chunk>>>, } /// A single chunk in a world (16*?*16 blocks). This only contains the blocks @@ -214,7 +215,7 @@ impl ChunkStorage { ChunkStorage { height, min_y, - map: HashMap::new(), + map: IntMap::default(), } } diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index 0d80f75d..55e47676 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -3,7 +3,7 @@ #![feature(error_generic_member_access)] mod bit_storage; -mod chunk_storage; +pub mod chunk_storage; mod container; pub mod heightmap; pub mod iterators; @@ -13,9 +13,7 @@ mod world; use std::backtrace::Backtrace; pub use bit_storage::BitStorage; -pub use chunk_storage::{ - calculate_chunk_storage_range, Chunk, ChunkStorage, PartialChunkStorage, Section, -}; +pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage, Section}; pub use container::*; use thiserror::Error; pub use world::*; diff --git a/azalea/benches/pathfinder.rs b/azalea/benches/pathfinder.rs index 34b6f91c..e7c52d57 100644 --- a/azalea/benches/pathfinder.rs +++ b/azalea/benches/pathfinder.rs @@ -4,6 +4,7 @@ use azalea::{ pathfinder::{ astar::{self, a_star}, goals::BlockPosGoal, + moves::PathfinderCtx, Goal, }, BlockPos, @@ -73,13 +74,14 @@ fn generate_bedrock_world( fn bench_pathfinder(c: &mut Criterion) { c.bench_function("bedrock", |b| { let mut partial_chunks = PartialChunkStorage::new(32); - let successors_fn = azalea::pathfinder::moves::parkour::parkour_move; + let successors_fn = azalea::pathfinder::moves::default_move; b.iter(|| { let (world, start, end) = generate_bedrock_world(&mut partial_chunks, 4); + let ctx = PathfinderCtx::new(&world); let goal = BlockPosGoal(end); - let successors = |pos: BlockPos| successors_fn(&world, pos); + let successors = |pos: BlockPos| successors_fn(&ctx, pos); let astar::Path { movements, partial } = a_star( start, diff --git a/azalea/src/pathfinder/astar.rs b/azalea/src/pathfinder/astar.rs index 505b09c0..bc7b2309 100644 --- a/azalea/src/pathfinder/astar.rs +++ b/azalea/src/pathfinder/astar.rs @@ -26,14 +26,14 @@ const MIN_IMPROVEMENT: f32 = 0.01; pub fn a_star<P, M, HeuristicFn, SuccessorsFn, SuccessFn>( start: P, heuristic: HeuristicFn, - successors: SuccessorsFn, + mut successors: SuccessorsFn, success: SuccessFn, timeout: Duration, ) -> Path<P, M> where P: Eq + Hash + Copy + Debug, HeuristicFn: Fn(P) -> f32, - SuccessorsFn: Fn(P) -> Vec<Edge<P, M>>, + SuccessorsFn: FnMut(P) -> Vec<Edge<P, M>>, SuccessFn: Fn(P) -> bool, { let start_time = Instant::now(); diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 02b7d935..831a5524 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -19,6 +19,7 @@ use crate::ecs::{ query::{With, Without}, system::{Commands, Query, Res}, }; +use crate::pathfinder::moves::PathfinderCtx; use azalea_client::movement::walk_listener; use azalea_client::{StartSprintEvent, StartWalkEvent}; use azalea_core::position::BlockPos; @@ -178,7 +179,8 @@ fn goto_listener( debug!("start: {start:?}"); let world = &world_lock.read().chunks; - let successors = |pos: BlockPos| successors_fn(world, pos); + let ctx = PathfinderCtx::new(world); + let successors = |pos: BlockPos| successors_fn(&ctx, pos); let mut attempt_number = 0; @@ -192,15 +194,20 @@ fn goto_listener( |n| goal.heuristic(n), successors, |n| goal.success(n), - Duration::from_secs(if attempt_number == 0 { 1 } else { 5 }), + Duration::from_secs(if attempt_number == 0 { 10 } else { 10 }), ); let end_time = std::time::Instant::now(); debug!("partial: {partial:?}"); - debug!("time: {:?}", end_time - start_time); + let duration = end_time - start_time; + if partial { + info!("Pathfinder took {duration:?} (timed out)"); + } else { + info!("Pathfinder took {duration:?}"); + } - info!("Path:"); + debug!("Path:"); for movement in &movements { - info!(" {:?}", movement.target); + debug!(" {:?}", movement.target); } path = movements.into_iter().collect::<VecDeque<_>>(); @@ -275,11 +282,10 @@ fn path_found_listener( let world_lock = instance_container.get(instance_name).expect( "Entity tried to pathfind but the entity isn't in a valid world", ); + let world = &world_lock.read().chunks; + let ctx = PathfinderCtx::new(&world); let successors_fn: moves::SuccessorsFn = event.successors_fn; - let successors = |pos: BlockPos| { - let world = &world_lock.read().chunks; - successors_fn(world, pos) - }; + let successors = |pos: BlockPos| successors_fn(&ctx, pos); if successors(last_node.target) .iter() @@ -439,10 +445,9 @@ fn tick_execute_path( { // obstruction check (the path we're executing isn't possible anymore) - let successors = |pos: BlockPos| { - let world = &world_lock.read().chunks; - successors_fn(world, pos) - }; + let world = &world_lock.read().chunks; + let ctx = PathfinderCtx::new(&world); + let successors = |pos: BlockPos| successors_fn(&ctx, pos); if let Some(last_reached_node) = pathfinder.last_reached_node { if let Some(obstructed_index) = diff --git a/azalea/src/pathfinder/moves/basic.rs b/azalea/src/pathfinder/moves/basic.rs index 1785ec2e..b7bb1116 100644 --- a/azalea/src/pathfinder/moves/basic.rs +++ b/azalea/src/pathfinder/moves/basic.rs @@ -5,33 +5,29 @@ use azalea_core::{ direction::CardinalDirection, position::{BlockPos, Vec3}, }; -use azalea_world::ChunkStorage; use crate::{ pathfinder::{astar, costs::*}, JumpEvent, LookAtEvent, }; -use super::{ - default_is_reached, fall_distance, is_block_passable, is_passable, is_standable, Edge, - ExecuteCtx, IsReachedCtx, MoveData, -}; +use super::{default_is_reached, Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx}; -pub fn basic_move(world: &ChunkStorage, node: BlockPos) -> Vec<Edge> { +pub fn basic_move(ctx: &PathfinderCtx, node: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); - edges.extend(forward_move(world, node)); - edges.extend(ascend_move(world, node)); - edges.extend(descend_move(world, node)); - edges.extend(diagonal_move(world, node)); + edges.extend(forward_move(ctx, node)); + edges.extend(ascend_move(ctx, node)); + edges.extend(descend_move(ctx, node)); + edges.extend(diagonal_move(ctx, node)); edges } -fn forward_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn forward_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let offset = BlockPos::new(dir.x(), 0, dir.z()); - if !is_standable(&(pos + offset), world) { + if !ctx.is_standable(&(pos + offset)) { continue; } @@ -72,15 +68,15 @@ fn execute_forward_move( }); } -fn ascend_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn ascend_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let offset = BlockPos::new(dir.x(), 1, dir.z()); - if !is_block_passable(&pos.up(2), world) { + if !ctx.is_block_passable(&pos.up(2)) { continue; } - if !is_standable(&(pos + offset), world) { + if !ctx.is_standable(&(pos + offset)) { continue; } @@ -156,23 +152,23 @@ pub fn ascend_is_reached( BlockPos::from(position) == target || BlockPos::from(position) == target.down(1) } -fn descend_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn descend_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let dir_delta = BlockPos::new(dir.x(), 0, dir.z()); let new_horizontal_position = pos + dir_delta; - let fall_distance = fall_distance(&new_horizontal_position, world); + let fall_distance = ctx.fall_distance(&new_horizontal_position); if fall_distance == 0 || fall_distance > 3 { continue; } let new_position = new_horizontal_position.down(fall_distance as i32); // check whether 3 blocks vertically forward are passable - if !is_passable(&new_horizontal_position, world) { + if !ctx.is_passable(&new_horizontal_position) { continue; } // check whether we can stand on the target position - if !is_standable(&new_position, world) { + if !ctx.is_standable(&new_position) { continue; } @@ -258,22 +254,22 @@ pub fn descend_is_reached( && (position.y - target.y as f64) < 0.5 } -fn diagonal_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn diagonal_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let right = dir.right(); let offset = BlockPos::new(dir.x() + right.x(), 0, dir.z() + right.z()); - if !is_passable( - &BlockPos::new(pos.x + dir.x(), pos.y, pos.z + dir.z()), - world, - ) && !is_passable( - &BlockPos::new(pos.x + dir.right().x(), pos.y, pos.z + dir.right().z()), - world, - ) { + if !ctx.is_passable(&BlockPos::new(pos.x + dir.x(), pos.y, pos.z + dir.z())) + && !ctx.is_passable(&BlockPos::new( + pos.x + dir.right().x(), + pos.y, + pos.z + dir.right().z(), + )) + { continue; } - if !is_standable(&(pos + offset), world) { + if !ctx.is_standable(&(pos + offset)) { continue; } // +0.001 so it doesn't unnecessarily go diagonal sometimes diff --git a/azalea/src/pathfinder/moves/mod.rs b/azalea/src/pathfinder/moves/mod.rs index 197229fd..caf4bbb4 100644 --- a/azalea/src/pathfinder/moves/mod.rs +++ b/azalea/src/pathfinder/moves/mod.rs @@ -1,21 +1,22 @@ pub mod basic; pub mod parkour; -use std::fmt::Debug; +use std::{cell::RefCell, fmt::Debug}; use crate::{JumpEvent, LookAtEvent}; use super::astar; +use azalea_block::BlockState; use azalea_client::{StartSprintEvent, StartWalkEvent}; -use azalea_core::position::{BlockPos, Vec3}; +use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, Vec3}; use azalea_physics::collision::{self, BlockWithShape}; use azalea_world::ChunkStorage; use bevy_ecs::{entity::Entity, event::EventWriter}; +use nohash_hasher::IntMap; type Edge = astar::Edge<BlockPos, MoveData>; -pub type SuccessorsFn = - fn(&azalea_world::ChunkStorage, BlockPos) -> Vec<astar::Edge<BlockPos, MoveData>>; +pub type SuccessorsFn = fn(&PathfinderCtx, BlockPos) -> Vec<astar::Edge<BlockPos, MoveData>>; #[derive(Clone)] pub struct MoveData { @@ -33,71 +34,110 @@ impl Debug for MoveData { } } -/// whether this block is passable -fn is_block_passable(pos: &BlockPos, world: &ChunkStorage) -> bool { - let Some(block) = world.get_block_state(pos) else { - return false; - }; - if block.is_air() { - // fast path - return true; - } - if block.shape() != &*collision::EMPTY_SHAPE { - return false; - } - if block == azalea_registry::Block::Water.into() { - return false; - } - if block.waterlogged() { - return false; +pub struct PathfinderCtx<'a> { + world: &'a ChunkStorage, + cached_chunks: RefCell<IntMap<ChunkPos, Vec<azalea_world::Section>>>, +} + +impl<'a> PathfinderCtx<'a> { + pub fn new(world: &'a ChunkStorage) -> Self { + Self { + world, + cached_chunks: Default::default(), + } } - // block.waterlogged currently doesn't account for seagrass and some other water - // blocks - if block == azalea_registry::Block::Seagrass.into() { - return false; + + fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> { + let chunk_pos = ChunkPos::from(pos); + + let mut cached_chunks = self.cached_chunks.borrow_mut(); + if let Some(sections) = cached_chunks.get(&chunk_pos) { + return azalea_world::chunk_storage::get_block_state_from_sections( + sections, + &ChunkBlockPos::from(pos), + self.world.min_y, + ); + } + + let chunk = self.world.get(&chunk_pos)?; + let chunk = chunk.read(); + + cached_chunks.insert(chunk_pos, chunk.sections.clone()); + + azalea_world::chunk_storage::get_block_state_from_sections( + &chunk.sections, + &ChunkBlockPos::from(pos), + self.world.min_y, + ) } - true -} + /// whether this block is passable + pub fn is_block_passable(&self, pos: &BlockPos) -> bool { + let Some(block) = self.get_block_state(pos) else { + return false; + }; + if block.is_air() { + // fast path + return true; + } + if block.shape() != &*collision::EMPTY_SHAPE { + return false; + } + if block == azalea_registry::Block::Water.into() { + return false; + } + if block.waterlogged() { + return false; + } + // block.waterlogged currently doesn't account for seagrass and some other water + // blocks + if block == azalea_registry::Block::Seagrass.into() { + return false; + } -/// whether this block has a solid hitbox (i.e. we can stand on it) -fn is_block_solid(pos: &BlockPos, world: &ChunkStorage) -> bool { - let Some(block) = world.get_block_state(pos) else { - return false; - }; - if block.is_air() { - // fast path - return false; + true } - block.shape() == &*collision::BLOCK_SHAPE -} -/// Whether this block and the block above are passable -fn is_passable(pos: &BlockPos, world: &ChunkStorage) -> bool { - is_block_passable(pos, world) && is_block_passable(&pos.up(1), world) -} + /// whether this block has a solid hitbox (i.e. we can stand on it) + pub fn is_block_solid(&self, pos: &BlockPos) -> bool { + let Some(block) = self.get_block_state(pos) else { + return false; + }; + if block.is_air() { + // fast path + return false; + } + block.shape() == &*collision::BLOCK_SHAPE + } -/// Whether we can stand in this position. Checks if the block below is solid, -/// and that the two blocks above that are passable. + /// Whether this block and the block above are passable + pub fn is_passable(&self, pos: &BlockPos) -> bool { + self.is_block_passable(pos) && self.is_block_passable(&pos.up(1)) + } -fn is_standable(pos: &BlockPos, world: &ChunkStorage) -> bool { - is_block_solid(&pos.down(1), world) && is_passable(pos, world) -} + /// Whether we can stand in this position. Checks if the block below is + /// solid, and that the two blocks above that are passable. -/// Get the amount of air blocks until the next solid block below this one. -fn fall_distance(pos: &BlockPos, world: &ChunkStorage) -> u32 { - let mut distance = 0; - let mut current_pos = pos.down(1); - while is_block_passable(¤t_pos, world) { - distance += 1; - current_pos = current_pos.down(1); + pub fn is_standable(&self, pos: &BlockPos) -> bool { + self.is_block_solid(&pos.down(1)) && self.is_passable(pos) + } + + /// Get the amount of air blocks until the next solid block below this one. + pub fn fall_distance(&self, pos: &BlockPos) -> u32 { + let mut distance = 0; + let mut current_pos = pos.down(1); + while self.is_block_passable(¤t_pos) { + distance += 1; + current_pos = current_pos.down(1); - if current_pos.y < world.min_y { - return u32::MAX; + if current_pos.y < self.world.min_y { + return u32::MAX; + } } + distance } - distance } + pub struct ExecuteCtx<'w1, 'w2, 'w3, 'w4, 'a> { pub entity: Entity, /// The node that we're trying to reach. @@ -121,10 +161,10 @@ pub struct IsReachedCtx<'a> { pub physics: &'a azalea_entity::Physics, } -pub fn default_move(world: &ChunkStorage, node: BlockPos) -> Vec<Edge> { +pub fn default_move(ctx: &PathfinderCtx, node: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); - edges.extend(basic::basic_move(world, node)); - edges.extend(parkour::parkour_move(world, node)); + edges.extend(basic::basic_move(ctx, node)); + edges.extend(parkour::parkour_move(ctx, node)); edges } @@ -163,8 +203,9 @@ mod tests { .chunks .set_block_state(&BlockPos::new(0, 1, 0), BlockState::AIR, &world); - assert!(!is_block_passable(&BlockPos::new(0, 0, 0), &world)); - assert!(is_block_passable(&BlockPos::new(0, 1, 0), &world)); + let ctx = PathfinderCtx::new(&world); + assert!(!ctx.is_block_passable(&BlockPos::new(0, 0, 0))); + assert!(ctx.is_block_passable(&BlockPos::new(0, 1, 0),)); } #[test] @@ -183,8 +224,9 @@ mod tests { .chunks .set_block_state(&BlockPos::new(0, 1, 0), BlockState::AIR, &world); - assert!(is_block_solid(&BlockPos::new(0, 0, 0), &world)); - assert!(!is_block_solid(&BlockPos::new(0, 1, 0), &world)); + let ctx = PathfinderCtx::new(&world); + assert!(ctx.is_block_solid(&BlockPos::new(0, 0, 0))); + assert!(!ctx.is_block_solid(&BlockPos::new(0, 1, 0))); } #[test] @@ -209,8 +251,9 @@ mod tests { .chunks .set_block_state(&BlockPos::new(0, 3, 0), BlockState::AIR, &world); - assert!(is_standable(&BlockPos::new(0, 1, 0), &world)); - assert!(!is_standable(&BlockPos::new(0, 0, 0), &world)); - assert!(!is_standable(&BlockPos::new(0, 2, 0), &world)); + let ctx = PathfinderCtx::new(&world); + assert!(ctx.is_standable(&BlockPos::new(0, 1, 0))); + assert!(!ctx.is_standable(&BlockPos::new(0, 0, 0))); + assert!(!ctx.is_standable(&BlockPos::new(0, 2, 0))); } } diff --git a/azalea/src/pathfinder/moves/parkour.rs b/azalea/src/pathfinder/moves/parkour.rs index 03635faa..53f5a348 100644 --- a/azalea/src/pathfinder/moves/parkour.rs +++ b/azalea/src/pathfinder/moves/parkour.rs @@ -1,46 +1,42 @@ use azalea_client::{SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection}; use azalea_core::{direction::CardinalDirection, position::BlockPos}; -use azalea_world::ChunkStorage; use crate::{ pathfinder::{astar, costs::*}, JumpEvent, LookAtEvent, }; -use super::{ - default_is_reached, is_block_passable, is_block_solid, is_passable, is_standable, Edge, - ExecuteCtx, IsReachedCtx, MoveData, -}; +use super::{default_is_reached, Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx}; -pub fn parkour_move(world: &ChunkStorage, node: BlockPos) -> Vec<Edge> { +pub fn parkour_move(ctx: &PathfinderCtx, node: BlockPos) -> Vec<Edge> { 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.extend(parkour_forward_1_move(ctx, node)); + edges.extend(parkour_headhitter_forward_1_move(ctx, node)); + edges.extend(parkour_forward_2_move(ctx, node)); edges } -fn parkour_forward_1_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn parkour_forward_1_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let gap_offset = BlockPos::new(dir.x(), 0, dir.z()); let offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2); - if !is_standable(&(pos + offset), world) { + if !ctx.is_standable(&(pos + offset)) { continue; } - if !is_passable(&(pos + gap_offset), world) { + if !ctx.is_passable(&(pos + gap_offset)) { continue; } - if !is_block_passable(&(pos + gap_offset).up(2), world) { + if !ctx.is_block_passable(&(pos + gap_offset).up(2)) { continue; } // make sure we actually have to jump - if is_block_solid(&(pos + gap_offset).down(1), world) { + if ctx.is_block_solid(&(pos + gap_offset).down(1)) { continue; } // make sure it's not a headhitter - if !is_block_passable(&pos.up(2), world) { + if !ctx.is_block_passable(&pos.up(2)) { continue; } @@ -61,34 +57,34 @@ fn parkour_forward_1_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { edges } -fn parkour_forward_2_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn parkour_forward_2_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let gap_1_offset = BlockPos::new(dir.x(), 0, dir.z()); 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) { + if !ctx.is_standable(&(pos + offset)) { continue; } - if !is_passable(&(pos + gap_1_offset), world) { + if !ctx.is_passable(&(pos + gap_1_offset)) { continue; } - if !is_block_passable(&(pos + gap_1_offset).up(2), world) { + if !ctx.is_block_passable(&(pos + gap_1_offset).up(2)) { continue; } - if !is_passable(&(pos + gap_2_offset), world) { + if !ctx.is_passable(&(pos + gap_2_offset)) { continue; } - if !is_block_passable(&(pos + gap_2_offset).up(2), world) { + if !ctx.is_block_passable(&(pos + gap_2_offset).up(2)) { continue; } // make sure we actually have to jump - if is_block_solid(&(pos + gap_1_offset).down(1), world) { + if ctx.is_block_solid(&(pos + gap_1_offset).down(1)) { continue; } // make sure it's not a headhitter - if !is_block_passable(&pos.up(2), world) { + if !ctx.is_block_passable(&pos.up(2)) { continue; } @@ -112,27 +108,27 @@ fn parkour_forward_2_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { edges } -fn parkour_headhitter_forward_1_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn parkour_headhitter_forward_1_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let gap_offset = BlockPos::new(dir.x(), 0, dir.z()); let offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2); - if !is_standable(&(pos + offset), world) { + if !ctx.is_standable(&(pos + offset)) { continue; } - if !is_passable(&(pos + gap_offset), world) { + if !ctx.is_passable(&(pos + gap_offset)) { continue; } - if !is_block_passable(&(pos + gap_offset).up(2), world) { + if !ctx.is_block_passable(&(pos + gap_offset).up(2)) { continue; } // make sure we actually have to jump - if is_block_solid(&(pos + gap_offset).down(1), world) { + if ctx.is_block_solid(&(pos + gap_offset).down(1)) { continue; } // make sure it is a headhitter - if !is_block_solid(&pos.up(2), world) { + if !ctx.is_block_solid(&pos.up(2)) { continue; } |
