aboutsummaryrefslogtreecommitdiff
path: root/azalea
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2023-08-25 02:34:31 -0500
committermat <git@matdoes.dev>2023-08-25 02:34:31 -0500
commitd5465cd28e43d48b3e913fdb1161eb907e4d80d0 (patch)
treeb0962ac1bd09b434c67296c038ef3b26245ce6d7 /azalea
parent9c31f8033f006d5f505ce97e359638d6c1136859 (diff)
downloadazalea-drasl-d5465cd28e43d48b3e913fdb1161eb907e4d80d0.tar.xz
add basic pathfinding test
Diffstat (limited to 'azalea')
-rw-r--r--azalea/Cargo.toml1
-rwxr-xr-xazalea/examples/echo.rs17
-rw-r--r--azalea/examples/steal.rs79
-rw-r--r--azalea/examples/testbot.rs7
-rw-r--r--azalea/src/pathfinder/mod.rs59
-rw-r--r--azalea/src/pathfinder/moves.rs24
-rw-r--r--azalea/src/pathfinder/simulation.rs109
7 files changed, 227 insertions, 69 deletions
diff --git a/azalea/Cargo.toml b/azalea/Cargo.toml
index 527088f8..3e7aaaa2 100644
--- a/azalea/Cargo.toml
+++ b/azalea/Cargo.toml
@@ -41,6 +41,7 @@ tokio = "^1.31.0"
uuid = "1.4.1"
bevy_log = "0.11.1"
azalea-entity = { version = "0.1.0", path = "../azalea-entity" }
+bevy_time = "0.11.2"
[features]
default = ["log"]
diff --git a/azalea/examples/echo.rs b/azalea/examples/echo.rs
index dbf56a31..01390982 100755
--- a/azalea/examples/echo.rs
+++ b/azalea/examples/echo.rs
@@ -18,16 +18,13 @@ async fn main() {
pub struct State {}
async fn handle(bot: Client, event: Event, _state: State) -> anyhow::Result<()> {
- match event {
- Event::Chat(m) => {
- if let (Some(sender), content) = m.split_sender_and_content() {
- if sender == bot.profile.name {
- return Ok(()); // ignore our own messages
- }
- bot.chat(&content);
- };
- }
- _ => {}
+ if let Event::Chat(m) = event {
+ if let (Some(sender), content) = m.split_sender_and_content() {
+ if sender == bot.profile.name {
+ return Ok(()); // ignore our own messages
+ }
+ bot.chat(&content);
+ };
}
Ok(())
diff --git a/azalea/examples/steal.rs b/azalea/examples/steal.rs
index 7a7ee4bb..9bbda945 100644
--- a/azalea/examples/steal.rs
+++ b/azalea/examples/steal.rs
@@ -24,52 +24,49 @@ struct State {
}
async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
- match event {
- Event::Chat(m) => {
- if m.username() == Some(bot.profile.name.clone()) {
- return Ok(());
- };
- if m.content() != "go" {
- return Ok(());
- }
- {
- state.checked_chests.lock().clear();
- }
+ if let Event::Chat(m) = event {
+ if m.username() == Some(bot.profile.name.clone()) {
+ return Ok(());
+ };
+ if m.content() != "go" {
+ return Ok(());
+ }
+ {
+ state.checked_chests.lock().clear();
+ }
- let chest_block = bot
- .world()
- .read()
- .find_block(bot.position(), &azalea::Block::Chest.into());
- // TODO: update this when find_blocks is implemented
- let Some(chest_block) = chest_block else {
- bot.chat("No chest found");
- return Ok(());
- };
- // bot.goto(BlockPosGoal::from(chest_block));
- let Some(chest) = bot.open_container(chest_block).await else {
- println!("Couldn't open chest");
- return Ok(());
- };
+ let chest_block = bot
+ .world()
+ .read()
+ .find_block(bot.position(), &azalea::Block::Chest.into());
+ // TODO: update this when find_blocks is implemented
+ let Some(chest_block) = chest_block else {
+ bot.chat("No chest found");
+ return Ok(());
+ };
+ // bot.goto(BlockPosGoal::from(chest_block));
+ let Some(chest) = bot.open_container(chest_block).await else {
+ println!("Couldn't open chest");
+ return Ok(());
+ };
- println!("Getting contents");
- for (index, slot) in chest
- .contents()
- .expect("we just opened the chest")
- .iter()
- .enumerate()
- {
- println!("Checking slot {index}: {slot:?}");
- if let ItemSlot::Present(item) = slot {
- if item.kind == azalea::Item::Diamond {
- println!("clicking slot ^");
- chest.click(QuickMoveClick::Left { slot: index as u16 });
- }
+ println!("Getting contents");
+ for (index, slot) in chest
+ .contents()
+ .expect("we just opened the chest")
+ .iter()
+ .enumerate()
+ {
+ println!("Checking slot {index}: {slot:?}");
+ if let ItemSlot::Present(item) = slot {
+ if item.kind == azalea::Item::Diamond {
+ println!("clicking slot ^");
+ chest.click(QuickMoveClick::Left { slot: index as u16 });
}
}
-
- println!("Done");
}
- _ => {}
+
+ println!("Done");
}
Ok(())
diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs
index 3d566410..14800e9c 100644
--- a/azalea/examples/testbot.rs
+++ b/azalea/examples/testbot.rs
@@ -284,12 +284,11 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
_ => {}
}
}
- Event::Packet(packet) => match *packet {
- ClientboundGamePacket::Login(_) => {
+ Event::Packet(packet) => {
+ if let ClientboundGamePacket::Login(_) = *packet {
println!("login packet");
}
- _ => {}
- },
+ }
_ => {}
}
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<RwLock<Instance>>,
+}
+
+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::<Position>(self.entity).unwrap()
+ }
+}