1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

pathfinder can now handle slabs, stairs, and dirt paths

This commit is contained in:
mat 2025-05-06 10:58:48 -10:00
parent af3affb467
commit f7c9419045
5 changed files with 88 additions and 8 deletions

View file

@ -18,6 +18,7 @@ write down most non-trivial breaking changes.
- `StartJoinServerEvent` can now be used to join servers exclusively from the ECS without a Tokio runtime.
- `FormattedText::to_html` and `FormattedText::to_custom_format`.
- Add auto-reconnecting which is enabled by default.
- The pathfinder no longer avoids slabs, stairs, and dirt path blocks.
### Changed

View file

@ -135,9 +135,12 @@ fn execute_ascend_move(mut ctx: ExecuteCtx) {
}
if BlockPos::from(position) == start {
// only jump if the target is more than 0.5 blocks above us
if target.y as f64 - position.y > 0.5 {
ctx.jump();
}
}
}
#[must_use]
pub fn ascend_is_reached(
IsReachedCtx {

View file

@ -191,10 +191,19 @@ pub struct IsReachedCtx<'a> {
#[must_use]
pub fn default_is_reached(
IsReachedCtx {
position, target, ..
position,
target,
physics,
..
}: IsReachedCtx,
) -> bool {
BlockPos::from(position) == target
if BlockPos::from(position) == target {
return true;
}
// this is to make it handle things like slabs correctly, if we're on the block
// below the target but on_ground
BlockPos::from(position).up(1) == target && physics.on_ground()
}
pub struct PathfinderCtx<'a> {

View file

@ -6,6 +6,11 @@ use super::{Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx};
use crate::pathfinder::{astar, costs::*, rel_block_pos::RelBlockPos};
pub fn parkour_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
if !ctx.world.is_block_solid(node.down(1)) {
// we can only parkour from solid blocks (not just standable blocks like slabs)
return;
}
parkour_forward_1_move(ctx, node);
parkour_forward_2_move(ctx, node);
parkour_forward_3_move(ctx, node);
@ -232,9 +237,18 @@ fn execute_parkour_move(mut ctx: ExecuteCtx) {
#[must_use]
pub fn parkour_is_reached(
IsReachedCtx {
position, target, ..
position,
target,
physics,
..
}: IsReachedCtx,
) -> bool {
// 0.094 and not 0 for lilypads
BlockPos::from(position) == target && (position.y - target.y as f64) < 0.094
if BlockPos::from(position) == target && (position.y - target.y as f64) < 0.094 {
return true;
}
// this is to make it handle things like slabs correctly, if we're on the block
// below the target but on_ground
BlockPos::from(position).up(1) == target && physics.on_ground()
}

View file

@ -81,8 +81,12 @@ impl CachedSections {
pub struct CachedSection {
pub pos: ChunkSectionPos,
/// Blocks that we can fully pass through (like air).
pub passable_bitset: FixedBitSet<{ 4096_usize.div_ceil(8) }>,
/// Blocks that we can stand on and do parkour from.
pub solid_bitset: FixedBitSet<{ 4096_usize.div_ceil(8) }>,
/// Blocks that we can stand on but might not be able to parkour from.
pub standable_bitset: FixedBitSet<{ 4096_usize.div_ceil(8) }>,
}
impl CachedWorld {
@ -193,6 +197,7 @@ impl CachedWorld {
self.with_section(section_pos, |section| {
let mut passable_bitset = FixedBitSet::<{ 4096_usize.div_ceil(8) }>::new();
let mut solid_bitset = FixedBitSet::<{ 4096_usize.div_ceil(8) }>::new();
let mut standable_bitset = FixedBitSet::<{ 4096_usize.div_ceil(8) }>::new();
for i in 0..4096 {
let block_state = section.get_at_index(i);
if is_block_state_passable(block_state) {
@ -201,11 +206,15 @@ impl CachedWorld {
if is_block_state_solid(block_state) {
solid_bitset.set(i);
}
if is_block_state_standable(block_state) {
standable_bitset.set(i);
}
}
CachedSection {
pos: section_pos,
passable_bitset,
solid_bitset,
standable_bitset,
}
})
}
@ -235,6 +244,9 @@ impl CachedWorld {
pub fn is_block_solid(&self, pos: RelBlockPos) -> bool {
self.is_block_pos_solid(pos.apply(self.origin))
}
pub fn is_block_standable(&self, pos: RelBlockPos) -> bool {
self.is_block_pos_standable(pos.apply(self.origin))
}
fn is_block_pos_solid(&self, pos: BlockPos) -> bool {
let (section_pos, section_block_pos) =
@ -253,6 +265,23 @@ impl CachedWorld {
cached_blocks.insert(cached);
solid
}
fn is_block_pos_standable(&self, pos: BlockPos) -> bool {
let (section_pos, section_block_pos) =
(ChunkSectionPos::from(pos), ChunkSectionBlockPos::from(pos));
let index = u16::from(section_block_pos) as usize;
// SAFETY: we're only accessing this from one thread
let cached_blocks = unsafe { &mut *self.cached_blocks.get() };
if let Some(cached) = cached_blocks.get_mut(section_pos) {
return cached.standable_bitset.index(index);
}
let Some(cached) = self.calculate_bitsets_for_section(section_pos) else {
return false;
};
let solid = cached.standable_bitset.index(index);
cached_blocks.insert(cached);
solid
}
/// Returns how much it costs to break this block. Returns 0 if the block is
/// already passable.
@ -434,11 +463,11 @@ impl CachedWorld {
self.is_standable_at_block_pos(pos.apply(self.origin))
}
fn is_standable_at_block_pos(&self, pos: BlockPos) -> bool {
self.is_block_pos_solid(pos.down(1)) && self.is_passable_at_block_pos(pos)
self.is_block_pos_standable(pos.down(1)) && self.is_passable_at_block_pos(pos)
}
pub fn cost_for_standing(&self, pos: RelBlockPos, mining_cache: &MiningCache) -> f32 {
if !self.is_block_solid(pos.down(1)) {
if !self.is_block_standable(pos.down(1)) {
return f32::INFINITY;
}
self.cost_for_passing(pos, mining_cache)
@ -501,7 +530,8 @@ pub fn is_block_state_passable(block: BlockState) -> bool {
true
}
/// whether this block has a solid hitbox (i.e. we can stand on it)
/// whether this block has a solid hitbox at the top (i.e. we can stand on it
/// and do parkour from it)
pub fn is_block_state_solid(block: BlockState) -> bool {
if block.is_air() {
// fast path
@ -510,6 +540,29 @@ pub fn is_block_state_solid(block: BlockState) -> bool {
block.is_collision_shape_full()
}
pub fn is_block_state_standable(block: BlockState) -> bool {
if block.is_air() {
// fast path
return false;
}
if block.is_collision_shape_full() {
return true;
}
let registry_block = azalea_registry::Block::from(block);
if azalea_registry::tags::blocks::SLABS.contains(&registry_block)
|| azalea_registry::tags::blocks::STAIRS.contains(&registry_block)
{
return true;
}
if registry_block == azalea_registry::Block::DirtPath {
return true;
}
false
}
#[cfg(test)]
mod tests {