mirror of
https://github.com/mat-1/azalea.git
synced 2024-09-19 22:52:32 +00:00
optimize pathfinder more
This commit is contained in:
parent
c3d27487ca
commit
d0505f7de3
13 changed files with 217 additions and 158 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -327,6 +327,7 @@ dependencies = [
|
|||
"azalea-nbt",
|
||||
"azalea-registry",
|
||||
"bevy_ecs",
|
||||
"nohash-hasher",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"uuid",
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(¤t_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(¤t_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)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue