optimize pathfinder more

This commit is contained in:
mat 2023-10-02 17:51:38 -05:00
parent c3d27487ca
commit d0505f7de3
13 changed files with 217 additions and 158 deletions

1
Cargo.lock generated
View file

@ -327,6 +327,7 @@ dependencies = [
"azalea-nbt",
"azalea-registry",
"bevy_ecs",
"nohash-hasher",
"num-traits",
"serde",
"uuid",

View file

@ -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),

View file

@ -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),

View file

@ -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"

View file

@ -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)
}

View file

@ -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(),
}
}

View file

@ -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::*;

View file

@ -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,

View file

@ -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();

View file

@ -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) =

View file

@ -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

View file

@ -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;
}
// block.waterlogged currently doesn't account for seagrass and some other water
// blocks
if block == azalea_registry::Block::Seagrass.into() {
return false;
}
true
pub struct PathfinderCtx<'a> {
world: &'a ChunkStorage,
cached_chunks: RefCell<IntMap<ChunkPos, Vec<azalea_world::Section>>>,
}
/// 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;
}
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 we can stand in this position. Checks if the block below is solid,
/// and that the two blocks above that are passable.
fn is_standable(pos: &BlockPos, world: &ChunkStorage) -> bool {
is_block_solid(&pos.down(1), world) && is_passable(pos, world)
}
/// 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(&current_pos, world) {
distance += 1;
current_pos = current_pos.down(1);
if current_pos.y < world.min_y {
return u32::MAX;
impl<'a> PathfinderCtx<'a> {
pub fn new(world: &'a ChunkStorage) -> Self {
Self {
world,
cached_chunks: Default::default(),
}
}
distance
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,
)
}
/// 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;
}
true
}
/// 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 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))
}
/// Whether we can stand in this position. Checks if the block below is
/// solid, and that the two blocks above that are passable.
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(&current_pos) {
distance += 1;
current_pos = current_pos.down(1);
if current_pos.y < self.world.min_y {
return u32::MAX;
}
}
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)));
}
}

View file

@ -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;
}