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:
parent
af3affb467
commit
f7c9419045
5 changed files with 88 additions and 8 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -135,7 +135,10 @@ fn execute_ascend_move(mut ctx: ExecuteCtx) {
|
|||
}
|
||||
|
||||
if BlockPos::from(position) == start {
|
||||
ctx.jump();
|
||||
// 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]
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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(®istry_block)
|
||||
|| azalea_registry::tags::blocks::STAIRS.contains(®istry_block)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if registry_block == azalea_registry::Block::DirtPath {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue