diff options
| author | mat <git@matdoes.dev> | 2025-06-11 16:02:08 -0900 |
|---|---|---|
| committer | mat <git@matdoes.dev> | 2025-06-11 16:02:08 -0900 |
| commit | df092f25ec928a3ff6fb9c023d5cb66cd53d6196 (patch) | |
| tree | c5b98f0cb0935289652f7c7c041365c921684147 /azalea/src/pathfinder/tests.rs | |
| parent | 1b348ceeffc61e49b19f2982e7a9de479c1678de (diff) | |
| download | azalea-drasl-df092f25ec928a3ff6fb9c023d5cb66cd53d6196.tar.xz | |
add pathfinder retry_on_no_path and rework GotoEvent constructor
Diffstat (limited to 'azalea/src/pathfinder/tests.rs')
| -rw-r--r-- | azalea/src/pathfinder/tests.rs | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/azalea/src/pathfinder/tests.rs b/azalea/src/pathfinder/tests.rs new file mode 100644 index 00000000..782899e8 --- /dev/null +++ b/azalea/src/pathfinder/tests.rs @@ -0,0 +1,311 @@ + +use std::{ + collections::HashSet, + sync::Arc, + thread, + time::{Duration, Instant}, +}; + +use azalea_block::BlockState; +use azalea_core::position::{BlockPos, ChunkPos, Vec3}; +use azalea_world::{Chunk, ChunkStorage, PartialChunkStorage}; + +use super::{ + GotoEvent, + astar::PathfinderTimeout, + goals::BlockPosGoal, + moves, + simulation::{SimulatedPlayerBundle, Simulation}, +}; + +fn setup_blockposgoal_simulation( + partial_chunks: &mut PartialChunkStorage, + start_pos: BlockPos, + end_pos: BlockPos, + solid_blocks: &[BlockPos], +) -> Simulation { + let mut simulation = setup_simulation_world(partial_chunks, start_pos, solid_blocks, &[]); + + // you can uncomment this while debugging tests to get trace logs + // simulation.app.add_plugins(bevy_log::LogPlugin { + // level: bevy_log::Level::TRACE, + // filter: "".to_string(), + // ..Default::default() + // }); + + simulation.app.world_mut().send_event(GotoEvent { + entity: simulation.entity, + goal: Arc::new(BlockPosGoal(end_pos)), + successors_fn: moves::default_move, + allow_mining: false, + retry_on_no_path: true, + min_timeout: PathfinderTimeout::Nodes(1_000_000), + max_timeout: PathfinderTimeout::Nodes(5_000_000), + }); + simulation +} + +fn setup_simulation_world( + partial_chunks: &mut PartialChunkStorage, + start_pos: BlockPos, + solid_blocks: &[BlockPos], + extra_blocks: &[(BlockPos, BlockState)], +) -> Simulation { + let mut chunk_positions = HashSet::new(); + for block_pos in solid_blocks { + chunk_positions.insert(ChunkPos::from(block_pos)); + } + for (block_pos, _) in extra_blocks { + chunk_positions.insert(ChunkPos::from(block_pos)); + } + + let mut chunks = ChunkStorage::default(); + for chunk_pos in chunk_positions { + partial_chunks.set(&chunk_pos, Some(Chunk::default()), &mut chunks); + } + for block_pos in solid_blocks { + chunks.set_block_state(*block_pos, azalea_registry::Block::Stone.into()); + } + for (block_pos, block_state) in extra_blocks { + chunks.set_block_state(*block_pos, *block_state); + } + + let player = SimulatedPlayerBundle::new(Vec3::new( + start_pos.x as f64 + 0.5, + start_pos.y as f64, + start_pos.z as f64 + 0.5, + )); + Simulation::new(chunks, player) +} + +pub fn assert_simulation_reaches(simulation: &mut Simulation, ticks: usize, end_pos: BlockPos) { + wait_until_bot_starts_moving(simulation); + for _ in 0..ticks { + simulation.tick(); + } + assert_eq!(BlockPos::from(simulation.position()), end_pos); +} + +pub fn wait_until_bot_starts_moving(simulation: &mut Simulation) { + let start_pos = simulation.position(); + let start_time = Instant::now(); + while simulation.position() == start_pos + && !simulation.is_mining() + && start_time.elapsed() < Duration::from_millis(500) + { + simulation.tick(); + thread::yield_now(); + } +} + +#[test] +fn test_simple_forward() { + let mut partial_chunks = PartialChunkStorage::default(); + let mut simulation = setup_blockposgoal_simulation( + &mut partial_chunks, + BlockPos::new(0, 71, 0), + BlockPos::new(0, 71, 1), + &[BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 1)], + ); + assert_simulation_reaches(&mut simulation, 20, BlockPos::new(0, 71, 1)); +} + +#[test] +fn test_double_diagonal_with_walls() { + let mut partial_chunks = PartialChunkStorage::default(); + let mut simulation = setup_blockposgoal_simulation( + &mut partial_chunks, + BlockPos::new(0, 71, 0), + BlockPos::new(2, 71, 2), + &[ + BlockPos::new(0, 70, 0), + BlockPos::new(1, 70, 1), + BlockPos::new(2, 70, 2), + BlockPos::new(1, 72, 0), + BlockPos::new(2, 72, 1), + ], + ); + assert_simulation_reaches(&mut simulation, 30, BlockPos::new(2, 71, 2)); +} + +#[test] +fn test_jump_with_sideways_momentum() { + let mut partial_chunks = PartialChunkStorage::default(); + let mut simulation = setup_blockposgoal_simulation( + &mut partial_chunks, + BlockPos::new(0, 71, 3), + BlockPos::new(5, 76, 0), + &[ + BlockPos::new(0, 70, 3), + BlockPos::new(0, 70, 2), + BlockPos::new(0, 70, 1), + BlockPos::new(0, 70, 0), + BlockPos::new(1, 71, 0), + BlockPos::new(2, 72, 0), + BlockPos::new(3, 73, 0), + BlockPos::new(4, 74, 0), + BlockPos::new(5, 75, 0), + ], + ); + assert_simulation_reaches(&mut simulation, 120, BlockPos::new(5, 76, 0)); +} + +#[test] +fn test_parkour_2_block_gap() { + let mut partial_chunks = PartialChunkStorage::default(); + let mut simulation = setup_blockposgoal_simulation( + &mut partial_chunks, + BlockPos::new(0, 71, 0), + BlockPos::new(0, 71, 3), + &[BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 3)], + ); + assert_simulation_reaches(&mut simulation, 40, BlockPos::new(0, 71, 3)); +} + +#[test] +fn test_descend_and_parkour_2_block_gap() { + let mut partial_chunks = PartialChunkStorage::default(); + let mut simulation = setup_blockposgoal_simulation( + &mut partial_chunks, + BlockPos::new(0, 71, 0), + BlockPos::new(3, 67, 4), + &[ + BlockPos::new(0, 70, 0), + BlockPos::new(0, 69, 1), + BlockPos::new(0, 68, 2), + BlockPos::new(0, 67, 3), + BlockPos::new(0, 66, 4), + BlockPos::new(3, 66, 4), + ], + ); + assert_simulation_reaches(&mut simulation, 100, BlockPos::new(3, 67, 4)); +} + +#[test] +fn test_small_descend_and_parkour_2_block_gap() { + let mut partial_chunks = PartialChunkStorage::default(); + let mut simulation = setup_blockposgoal_simulation( + &mut partial_chunks, + BlockPos::new(0, 71, 0), + BlockPos::new(0, 70, 5), + &[ + BlockPos::new(0, 70, 0), + BlockPos::new(0, 70, 1), + BlockPos::new(0, 69, 2), + BlockPos::new(0, 69, 5), + ], + ); + assert_simulation_reaches(&mut simulation, 40, BlockPos::new(0, 70, 5)); +} + +#[test] +fn test_quickly_descend() { + let mut partial_chunks = PartialChunkStorage::default(); + let mut simulation = setup_blockposgoal_simulation( + &mut partial_chunks, + BlockPos::new(0, 71, 0), + BlockPos::new(0, 68, 3), + &[ + BlockPos::new(0, 70, 0), + BlockPos::new(0, 69, 1), + BlockPos::new(0, 68, 2), + BlockPos::new(0, 67, 3), + ], + ); + assert_simulation_reaches(&mut simulation, 60, BlockPos::new(0, 68, 3)); +} + +#[test] +fn test_2_gap_ascend_thrice() { + let mut partial_chunks = PartialChunkStorage::default(); + let mut simulation = setup_blockposgoal_simulation( + &mut partial_chunks, + BlockPos::new(0, 71, 0), + BlockPos::new(3, 74, 0), + &[ + BlockPos::new(0, 70, 0), + BlockPos::new(0, 71, 3), + BlockPos::new(3, 72, 3), + BlockPos::new(3, 73, 0), + ], + ); + assert_simulation_reaches(&mut simulation, 60, BlockPos::new(3, 74, 0)); +} + +#[test] +fn test_consecutive_3_gap_parkour() { + let mut partial_chunks = PartialChunkStorage::default(); + let mut simulation = setup_blockposgoal_simulation( + &mut partial_chunks, + BlockPos::new(0, 71, 0), + BlockPos::new(4, 71, 12), + &[ + BlockPos::new(0, 70, 0), + BlockPos::new(0, 70, 4), + BlockPos::new(0, 70, 8), + BlockPos::new(0, 70, 12), + BlockPos::new(4, 70, 12), + ], + ); + assert_simulation_reaches(&mut simulation, 80, BlockPos::new(4, 71, 12)); +} + +#[test] +fn test_jumps_with_more_sideways_momentum() { + let mut partial_chunks = PartialChunkStorage::default(); + let mut simulation = setup_blockposgoal_simulation( + &mut partial_chunks, + BlockPos::new(0, 71, 0), + BlockPos::new(4, 74, 9), + &[ + BlockPos::new(0, 70, 0), + BlockPos::new(0, 70, 1), + BlockPos::new(0, 70, 2), + BlockPos::new(0, 71, 3), + BlockPos::new(0, 72, 6), + BlockPos::new(0, 73, 9), + // this is the point where the bot might fall if it has too much momentum + BlockPos::new(2, 73, 9), + BlockPos::new(4, 73, 9), + ], + ); + assert_simulation_reaches(&mut simulation, 80, BlockPos::new(4, 74, 9)); +} + +#[test] +fn test_mine_through_non_colliding_block() { + let mut partial_chunks = PartialChunkStorage::default(); + + let mut simulation = setup_simulation_world( + &mut partial_chunks, + // the pathfinder can't actually dig straight down, so we start a block to the side so + // it can descend correctly + BlockPos::new(0, 72, 1), + &[BlockPos::new(0, 71, 1)], + &[ + ( + BlockPos::new(0, 71, 0), + azalea_registry::Block::SculkVein.into(), + ), + ( + BlockPos::new(0, 70, 0), + azalea_registry::Block::GrassBlock.into(), + ), + // this is an extra check to make sure that we don't accidentally break the block + // below (since tnt will break instantly) + (BlockPos::new(0, 69, 0), azalea_registry::Block::Tnt.into()), + ], + ); + + simulation.app.world_mut().send_event(GotoEvent { + entity: simulation.entity, + goal: Arc::new(BlockPosGoal(BlockPos::new(0, 69, 0))), + successors_fn: moves::default_move, + allow_mining: true, + retry_on_no_path: true, + min_timeout: PathfinderTimeout::Nodes(1_000_000), + max_timeout: PathfinderTimeout::Nodes(5_000_000), + }); + + assert_simulation_reaches(&mut simulation, 200, BlockPos::new(0, 70, 0)); +} |
