From d5465cd28e43d48b3e913fdb1161eb907e4d80d0 Mon Sep 17 00:00:00 2001 From: mat Date: Fri, 25 Aug 2023 02:34:31 -0500 Subject: add basic pathfinding test --- azalea/src/pathfinder/mod.rs | 59 ++++++++++++++++++- azalea/src/pathfinder/moves.rs | 24 ++++---- azalea/src/pathfinder/simulation.rs | 109 ++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 14 deletions(-) create mode 100644 azalea/src/pathfinder/simulation.rs (limited to 'azalea/src') diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 58b59fd4..d59f0046 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -1,6 +1,7 @@ mod astar; pub mod goals; mod moves; +pub mod simulation; use crate::bot::{JumpEvent, LookAtEvent}; use crate::pathfinder::astar::a_star; @@ -22,7 +23,7 @@ use azalea_entity::Local; use azalea_entity::{Physics, Position}; use azalea_physics::PhysicsSet; use azalea_world::{InstanceContainer, InstanceName}; -use bevy_app::{FixedUpdate, Update}; +use bevy_app::{FixedUpdate, PreUpdate, Update}; use bevy_ecs::prelude::Event; use bevy_ecs::query::Changed; use bevy_ecs::schedule::IntoSystemConfigs; @@ -44,11 +45,11 @@ impl Plugin for PathfinderPlugin { // (every 50 milliseconds). tick_execute_path.before(PhysicsSet), ) + .add_systems(PreUpdate, add_default_pathfinder) .add_systems( Update, ( goto_listener, - add_default_pathfinder, (handle_tasks, path_found_listener).chain(), stop_pathfinding_on_instance_change, ), @@ -342,3 +343,57 @@ impl Node { } } } + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use azalea_core::{BlockPos, ChunkPos, Vec3}; + use azalea_world::{Chunk, ChunkStorage, PartialChunkStorage}; + use bevy_log::LogPlugin; + + use super::{ + goals::BlockPosGoal, + simulation::{SimulatedPlayerBundle, Simulation}, + GotoEvent, + }; + + #[test] + fn test_simple_forward() { + let mut chunks = ChunkStorage::default(); + let mut partial_chunks = PartialChunkStorage::default(); + partial_chunks.set( + &ChunkPos { x: 0, z: 0 }, + Some(Chunk::default()), + &mut chunks, + ); + chunks.set_block_state( + &BlockPos::new(0, 70, 0), + azalea_registry::Block::Stone.into(), + ); + chunks.set_block_state( + &BlockPos::new(0, 70, 1), + azalea_registry::Block::Stone.into(), + ); + let player = SimulatedPlayerBundle::new(Vec3::new(0.5, 71., 0.5)); + let mut simulation = Simulation::new(chunks, player); + simulation.app.add_plugins(LogPlugin { + level: bevy_log::Level::DEBUG, + filter: "".to_string(), + }); + + simulation.app.world.send_event(GotoEvent { + entity: simulation.entity, + goal: Arc::new(BlockPosGoal::from(BlockPos::new(0, 71, 1))), + }); + + for _ in 0..20 { + simulation.tick(); + } + + assert_eq!( + BlockPos::from(simulation.position()), + BlockPos::new(0, 71, 1) + ); + } +} diff --git a/azalea/src/pathfinder/moves.rs b/azalea/src/pathfinder/moves.rs index 04e2e725..0cc211ac 100644 --- a/azalea/src/pathfinder/moves.rs +++ b/azalea/src/pathfinder/moves.rs @@ -192,17 +192,17 @@ mod tests { partial_world.chunks.set_block_state( &BlockPos::new(0, 0, 0), azalea_registry::Block::Stone.into(), - &mut chunk_storage, + &chunk_storage, ); partial_world.chunks.set_block_state( &BlockPos::new(0, 1, 0), BlockState::AIR, - &mut chunk_storage, + &chunk_storage, ); let world = chunk_storage.into(); - assert_eq!(is_block_passable(&BlockPos::new(0, 0, 0), &world), false); - assert_eq!(is_block_passable(&BlockPos::new(0, 1, 0), &world), true); + assert!(!is_block_passable(&BlockPos::new(0, 0, 0), &world)); + assert!(is_block_passable(&BlockPos::new(0, 1, 0), &world)); } #[test] @@ -217,17 +217,17 @@ mod tests { partial_world.chunks.set_block_state( &BlockPos::new(0, 0, 0), azalea_registry::Block::Stone.into(), - &mut chunk_storage, + &chunk_storage, ); partial_world.chunks.set_block_state( &BlockPos::new(0, 1, 0), BlockState::AIR, - &mut chunk_storage, + &chunk_storage, ); let world = chunk_storage.into(); - assert_eq!(is_block_solid(&BlockPos::new(0, 0, 0), &world), true); - assert_eq!(is_block_solid(&BlockPos::new(0, 1, 0), &world), false); + assert!(is_block_solid(&BlockPos::new(0, 0, 0), &world)); + assert!(!is_block_solid(&BlockPos::new(0, 1, 0), &world)); } #[test] @@ -242,22 +242,22 @@ mod tests { partial_world.chunks.set_block_state( &BlockPos::new(0, 0, 0), azalea_registry::Block::Stone.into(), - &mut chunk_storage, + &chunk_storage, ); partial_world.chunks.set_block_state( &BlockPos::new(0, 1, 0), BlockState::AIR, - &mut chunk_storage, + &chunk_storage, ); partial_world.chunks.set_block_state( &BlockPos::new(0, 2, 0), BlockState::AIR, - &mut chunk_storage, + &chunk_storage, ); partial_world.chunks.set_block_state( &BlockPos::new(0, 3, 0), BlockState::AIR, - &mut chunk_storage, + &chunk_storage, ); let world = chunk_storage.into(); diff --git a/azalea/src/pathfinder/simulation.rs b/azalea/src/pathfinder/simulation.rs new file mode 100644 index 00000000..372a8a3b --- /dev/null +++ b/azalea/src/pathfinder/simulation.rs @@ -0,0 +1,109 @@ +use std::{sync::Arc, time::Duration}; + +use azalea_client::PhysicsState; +use azalea_core::{ResourceLocation, Vec3}; +use azalea_entity::{ + attributes::AttributeInstance, metadata::Sprinting, Attributes, EntityDimensions, Physics, + Position, +}; +use azalea_world::{ChunkStorage, Instance, InstanceContainer, InstanceName, MinecraftEntityId}; +use bevy_app::{App, FixedUpdate}; +use bevy_ecs::prelude::*; +use bevy_time::fixed_timestep::FixedTime; +use parking_lot::RwLock; + +#[derive(Bundle, Clone)] +pub struct SimulatedPlayerBundle { + pub position: Position, + pub physics: Physics, + pub physics_state: PhysicsState, + pub attributes: Attributes, +} + +impl SimulatedPlayerBundle { + pub fn new(position: Vec3) -> Self { + let dimensions = EntityDimensions { + width: 0.6, + height: 1.8, + }; + + SimulatedPlayerBundle { + position: Position::new(position), + physics: Physics::new(dimensions, &position), + physics_state: PhysicsState::default(), + attributes: Attributes { + speed: AttributeInstance::new(0.1), + attack_speed: AttributeInstance::new(4.0), + }, + } + } +} + +/// Simulate the Minecraft world to see if certain movements would be possible. +pub struct Simulation { + pub app: App, + pub entity: Entity, + _instance: Arc>, +} + +impl Simulation { + pub fn new(chunks: ChunkStorage, player: SimulatedPlayerBundle) -> Self { + let instance_name = ResourceLocation::new("azalea:simulation"); + + let instance = Arc::new(RwLock::new(Instance { + chunks, + ..Default::default() + })); + + let mut app = App::new(); + // we don't use all the default azalea plugins because we don't need all of them + app.add_plugins(( + azalea_physics::PhysicsPlugin, + azalea_entity::EntityPlugin, + azalea_client::movement::PlayerMovePlugin, + super::PathfinderPlugin, + crate::BotPlugin, + azalea_client::task_pool::TaskPoolPlugin::default(), + )) + // make sure it doesn't do fixed ticks without us telling it to + .insert_resource(FixedTime::new(Duration::from_secs(60))) + .insert_resource(InstanceContainer { + worlds: [(instance_name.clone(), Arc::downgrade(&instance.clone()))] + .iter() + .cloned() + .collect(), + }); + + app.edit_schedule(bevy_app::Main, |schedule| { + schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded); + }); + + let entity = app + .world + .spawn(( + MinecraftEntityId(0), + InstanceName(instance_name), + azalea_entity::Local, + azalea_client::LocalPlayerInLoadedChunk, + azalea_entity::Jumping::default(), + azalea_entity::LookDirection::default(), + Sprinting(true), + azalea_entity::metadata::Player, + player, + )) + .id(); + + Self { + app, + entity, + _instance: instance, + } + } + pub fn tick(&mut self) { + self.app.world.run_schedule(FixedUpdate); + self.app.update(); + } + pub fn position(&self) -> Vec3 { + **self.app.world.get::(self.entity).unwrap() + } +} -- cgit v1.2.3