1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00
azalea/azalea/src/pathfinder/moves.rs
mat 9193c1b408 separate minecraft entity ids from azalea entity ids + more ecs stuff
i guess i'm using bevy_app now too huh
it's necessary for plugins and it lets us control the tick rate anyways so it's fine i think

i'm still not 100% sure how packet handling that interacts with the world will work, but i think if i can sneak the ecs world into there it'll be fine. Can't put packet handling in the schedule because that'd make it tick-bound, which it's not (technically it'd still work but it'd be wrong and anticheats might realize).
2023-01-01 03:12:48 -06:00

206 lines
6.4 KiB
Rust

use super::{Node, VerticalVel};
use azalea_core::{BlockPos, CardinalDirection};
use azalea_physics::collision::{self, BlockWithShape};
use azalea_world::World;
/// whether this block is passable
fn is_block_passable(pos: &BlockPos, world: &World) -> bool {
if let Some(block) = world.get_block_state(pos) {
block.shape() == &collision::empty_shape()
} else {
false
}
}
/// whether this block has a solid hitbox (i.e. we can stand on it)
fn is_block_solid(pos: &BlockPos, world: &World) -> bool {
if let Some(block) = world.get_block_state(pos) {
block.shape() == &collision::block_shape()
} else {
false
}
}
/// Whether this block and the block above are passable
fn is_passable(pos: &BlockPos, world: &World) -> 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: &World) -> bool {
is_block_solid(&pos.down(1), world) && is_passable(pos, world)
}
const JUMP_COST: f32 = 0.5;
const WALK_ONE_BLOCK_COST: f32 = 1.0;
pub trait Move {
fn cost(&self, world: &World, node: &Node) -> f32;
/// Returns by how much the entity's position should be changed when this
/// move is executed.
fn offset(&self) -> BlockPos;
fn next_node(&self, node: &Node) -> Node {
Node {
pos: node.pos + self.offset(),
vertical_vel: VerticalVel::None,
}
}
}
pub struct ForwardMove(pub CardinalDirection);
impl Move for ForwardMove {
fn cost(&self, world: &World, node: &Node) -> f32 {
if is_standable(&(node.pos + self.offset()), world)
&& node.vertical_vel == VerticalVel::None
{
WALK_ONE_BLOCK_COST
} else {
f32::INFINITY
}
}
fn offset(&self) -> BlockPos {
BlockPos::new(self.0.x(), 0, self.0.z())
}
}
pub struct AscendMove(pub CardinalDirection);
impl Move for AscendMove {
fn cost(&self, world: &World, node: &Node) -> f32 {
if node.vertical_vel == VerticalVel::None
&& is_block_passable(&node.pos.up(2), world)
&& is_standable(&(node.pos + self.offset()), world)
{
WALK_ONE_BLOCK_COST + JUMP_COST
} else {
f32::INFINITY
}
}
fn offset(&self) -> BlockPos {
BlockPos::new(self.0.x(), 1, self.0.z())
}
fn next_node(&self, node: &Node) -> Node {
Node {
pos: node.pos + self.offset(),
vertical_vel: VerticalVel::None,
}
}
}
pub struct DescendMove(pub CardinalDirection);
impl Move for DescendMove {
fn cost(&self, world: &World, node: &Node) -> f32 {
// check whether 3 blocks vertically forward are passable
if node.vertical_vel == VerticalVel::None
&& is_standable(&(node.pos + self.offset()), world)
&& is_block_passable(&(node.pos + self.offset().up(2)), world)
{
WALK_ONE_BLOCK_COST
} else {
f32::INFINITY
}
}
fn offset(&self) -> BlockPos {
BlockPos::new(self.0.x(), -1, self.0.z())
}
fn next_node(&self, node: &Node) -> Node {
Node {
pos: node.pos + self.offset(),
vertical_vel: VerticalVel::None,
}
}
}
pub struct DiagonalMove(pub CardinalDirection);
impl Move for DiagonalMove {
fn cost(&self, world: &World, node: &Node) -> f32 {
if node.vertical_vel != VerticalVel::None {
return f32::INFINITY;
}
if !is_passable(
&BlockPos::new(node.pos.x + self.0.x(), node.pos.y, node.pos.z + self.0.z()),
world,
) && !is_passable(
&BlockPos::new(
node.pos.x + self.0.right().x(),
node.pos.y,
node.pos.z + self.0.right().z(),
),
world,
) {
return f32::INFINITY;
}
if !is_standable(&(node.pos + self.offset()), world) {
return f32::INFINITY;
}
WALK_ONE_BLOCK_COST * 1.4
}
fn offset(&self) -> BlockPos {
let right = self.0.right();
BlockPos::new(self.0.x() + right.x(), 0, self.0.z() + right.z())
}
fn next_node(&self, node: &Node) -> Node {
Node {
pos: node.pos + self.offset(),
vertical_vel: VerticalVel::None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use azalea_block::BlockState;
use azalea_core::ChunkPos;
use azalea_world::{Chunk, PartialWorld};
#[test]
fn test_is_passable() {
let mut world = PartialWorld::default();
world
.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
.unwrap();
world.set_block_state(&BlockPos::new(0, 0, 0), BlockState::Stone);
world.set_block_state(&BlockPos::new(0, 1, 0), BlockState::Air);
assert_eq!(
is_block_passable(&BlockPos::new(0, 0, 0), &world.shared),
false
);
assert_eq!(
is_block_passable(&BlockPos::new(0, 1, 0), &world.shared),
true
);
}
#[test]
fn test_is_solid() {
let mut world = PartialWorld::default();
world
.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
.unwrap();
world.set_block_state(&BlockPos::new(0, 0, 0), BlockState::Stone);
world.set_block_state(&BlockPos::new(0, 1, 0), BlockState::Air);
assert_eq!(is_block_solid(&BlockPos::new(0, 0, 0), &world.shared), true);
assert_eq!(
is_block_solid(&BlockPos::new(0, 1, 0), &world.shared),
false
);
}
#[test]
fn test_is_standable() {
let mut world = PartialWorld::default();
world
.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
.unwrap();
world.set_block_state(&BlockPos::new(0, 0, 0), BlockState::Stone);
world.set_block_state(&BlockPos::new(0, 1, 0), BlockState::Air);
world.set_block_state(&BlockPos::new(0, 2, 0), BlockState::Air);
world.set_block_state(&BlockPos::new(0, 3, 0), BlockState::Air);
assert!(is_standable(&BlockPos::new(0, 1, 0), &world.shared));
assert!(!is_standable(&BlockPos::new(0, 0, 0), &world.shared));
assert!(!is_standable(&BlockPos::new(0, 2, 0), &world.shared));
}
}