aboutsummaryrefslogtreecommitdiff
path: root/azalea/src
diff options
context:
space:
mode:
Diffstat (limited to 'azalea/src')
-rw-r--r--azalea/src/bot.rs40
-rw-r--r--azalea/src/pathfinder/goals.rs7
-rw-r--r--azalea/src/pathfinder/mod.rs60
3 files changed, 94 insertions, 13 deletions
diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs
index 514cea1e..dd8e6209 100644
--- a/azalea/src/bot.rs
+++ b/azalea/src/bot.rs
@@ -1,8 +1,8 @@
use std::f64::consts::PI;
-use azalea_client::TickBroadcast;
use azalea_client::interact::SwingArmEvent;
use azalea_client::mining::Mining;
+use azalea_client::tick_broadcast::{TickBroadcast, UpdateBroadcast};
use azalea_core::position::{BlockPos, Vec3};
use azalea_core::tick::GameTick;
use azalea_entity::{
@@ -86,6 +86,12 @@ pub trait BotClientExt {
fn look_at(&self, pos: Vec3);
/// Get a receiver that will receive a message every tick.
fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>;
+ /// Get a receiver that will receive a message every ECS Update.
+ fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>;
+ /// Wait for one tick.
+ fn wait_one_tick(&self) -> impl Future<Output = ()> + Send;
+ /// Wait for one ECS Update.
+ fn wait_one_update(&self) -> impl Future<Output = ()> + Send;
/// Mine a block. This won't turn the bot's head towards the block, so if
/// that's necessary you'll have to do that yourself with [`look_at`].
///
@@ -133,6 +139,38 @@ impl BotClientExt for azalea_client::Client {
tick_broadcast.subscribe()
}
+ /// Returns a Receiver that receives a message every ECS Update.
+ ///
+ /// ECS Updates happen at least at the frequency of game ticks, usually
+ /// faster.
+ ///
+ /// This is useful if you're sending an ECS event and want to make sure it's
+ /// been handled before continuing.
+ fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()> {
+ let ecs = self.ecs.lock();
+ let update_broadcast = ecs.resource::<UpdateBroadcast>();
+ update_broadcast.subscribe()
+ }
+
+ /// Wait for one tick using [`Self::get_tick_broadcaster`].
+ ///
+ /// If you're going to run this in a loop, you may want to use that function
+ /// instead and use the `Receiver` from it as it'll be more efficient.
+ async fn wait_one_tick(&self) {
+ let mut receiver = self.get_tick_broadcaster();
+ // wait for the next tick
+ let _ = receiver.recv().await;
+ }
+ /// Waits for one ECS Update using [`Self::get_update_broadcaster`].
+ ///
+ /// If you're going to run this in a loop, you may want to use that function
+ /// instead and use the `Receiver` from it as it'll be more efficient.
+ async fn wait_one_update(&self) {
+ let mut receiver = self.get_update_broadcaster();
+ // wait for the next tick
+ let _ = receiver.recv().await;
+ }
+
async fn mine(&self, position: BlockPos) {
self.start_mining(position);
// vanilla sends an extra swing arm packet when we start mining
diff --git a/azalea/src/pathfinder/goals.rs b/azalea/src/pathfinder/goals.rs
index 0fb72446..9052c8fd 100644
--- a/azalea/src/pathfinder/goals.rs
+++ b/azalea/src/pathfinder/goals.rs
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
use super::costs::{COST_HEURISTIC, FALL_N_BLOCKS_COST, JUMP_ONE_BLOCK_COST};
-pub trait Goal: Debug {
+pub trait Goal: Debug + Send + Sync {
#[must_use]
fn heuristic(&self, n: BlockPos) -> f32;
#[must_use]
@@ -100,6 +100,11 @@ pub struct RadiusGoal {
pub pos: Vec3,
pub radius: f32,
}
+impl RadiusGoal {
+ pub fn new(pos: Vec3, radius: f32) -> Self {
+ Self { pos, radius }
+ }
+}
impl Goal for RadiusGoal {
fn heuristic(&self, n: BlockPos) -> f32 {
let n = n.center();
diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs
index 0db627ac..81ac5337 100644
--- a/azalea/src/pathfinder/mod.rs
+++ b/azalea/src/pathfinder/mod.rs
@@ -47,7 +47,6 @@ use self::debug::debug_render_path_with_particles;
use self::goals::Goal;
use self::mining::MiningCache;
use self::moves::{ExecuteCtx, IsReachedCtx, SuccessorsFn};
-use crate::WalkDirection;
use crate::app::{App, Plugin};
use crate::bot::{JumpEvent, LookAtEvent};
use crate::ecs::{
@@ -58,6 +57,7 @@ use crate::ecs::{
system::{Commands, Query, Res},
};
use crate::pathfinder::{astar::a_star, moves::PathfinderCtx, world::CachedWorld};
+use crate::{BotClientExt, WalkDirection};
#[derive(Clone, Default)]
pub struct PathfinderPlugin;
@@ -103,7 +103,7 @@ impl Plugin for PathfinderPlugin {
/// A component that makes this client able to pathfind.
#[derive(Component, Default, Clone)]
pub struct Pathfinder {
- pub goal: Option<Arc<dyn Goal + Send + Sync>>,
+ pub goal: Option<Arc<dyn Goal>>,
pub successors_fn: Option<SuccessorsFn>,
pub is_calculating: bool,
pub allow_mining: bool,
@@ -134,7 +134,7 @@ pub struct ExecutingPath {
pub struct GotoEvent {
/// The local bot entity that will do the pathfinding and execute the path.
pub entity: Entity,
- pub goal: Arc<dyn Goal + Send + Sync>,
+ pub goal: Arc<dyn Goal>,
/// The function that's used for checking what moves are possible. Usually
/// `pathfinder::moves::default_move`
pub successors_fn: SuccessorsFn,
@@ -180,22 +180,40 @@ pub fn add_default_pathfinder(
}
pub trait PathfinderClientExt {
- fn goto(&self, goal: impl Goal + Send + Sync + 'static);
- fn goto_without_mining(&self, goal: impl Goal + Send + Sync + 'static);
+ fn goto(&self, goal: impl Goal + 'static) -> impl Future<Output = ()>;
+ fn start_goto(&self, goal: impl Goal + 'static);
+ fn start_goto_without_mining(&self, goal: impl Goal + 'static);
fn stop_pathfinding(&self);
+ fn wait_until_goto_target_reached(&self) -> impl Future<Output = ()>;
+ fn is_goto_target_reached(&self) -> bool;
}
impl PathfinderClientExt for azalea_client::Client {
+ /// Pathfind to the given goal and wait until either the target is reached
+ /// or the pathfinding is canceled.
+ ///
+ /// ```
+ /// # use azalea::prelude::*;
+ /// # use azalea::{BlockPos, pathfinder::goals::BlockPosGoal};
+ /// # fn example(bot: &Client) {
+ /// bot.goto(BlockPosGoal(BlockPos::new(0, 70, 0))).await;
+ /// # }
+ /// ```
+ async fn goto(&self, goal: impl Goal + 'static) {
+ self.start_goto(goal);
+ self.wait_until_goto_target_reached().await;
+ }
+
/// Start pathfinding to a given goal.
///
/// ```
/// # use azalea::prelude::*;
/// # use azalea::{BlockPos, pathfinder::goals::BlockPosGoal};
/// # fn example(bot: &Client) {
- /// bot.goto(BlockPosGoal(BlockPos::new(0, 70, 0)));
+ /// bot.start_goto(BlockPosGoal(BlockPos::new(0, 70, 0)));
/// # }
/// ```
- fn goto(&self, goal: impl Goal + Send + Sync + 'static) {
+ fn start_goto(&self, goal: impl Goal + 'static) {
self.ecs.lock().send_event(GotoEvent {
entity: self.entity,
goal: Arc::new(goal),
@@ -206,9 +224,9 @@ impl PathfinderClientExt for azalea_client::Client {
});
}
- /// Same as [`goto`](Self::goto). but the bot won't break any blocks while
- /// executing the path.
- fn goto_without_mining(&self, goal: impl Goal + Send + Sync + 'static) {
+ /// Same as [`start_goto`](Self::start_goto). but the bot won't break any
+ /// blocks while executing the path.
+ fn start_goto_without_mining(&self, goal: impl Goal + 'static) {
self.ecs.lock().send_event(GotoEvent {
entity: self.entity,
goal: Arc::new(goal),
@@ -225,6 +243,26 @@ impl PathfinderClientExt for azalea_client::Client {
force: false,
});
}
+
+ /// Waits forever until the bot no longer has a pathfinder goal.
+ async fn wait_until_goto_target_reached(&self) {
+ // we do this to make sure the event got handled before we start checking
+ // is_goto_target_reached
+ self.wait_one_update().await;
+
+ let mut tick_broadcaster = self.get_tick_broadcaster();
+ while !self.is_goto_target_reached() {
+ // check every tick
+ tick_broadcaster.recv().await.unwrap();
+ }
+ }
+
+ fn is_goto_target_reached(&self) -> bool {
+ self.map_get_component::<Pathfinder, _>(|p| {
+ p.map(|p| p.goal.is_none() && !p.is_calculating)
+ .unwrap_or(true)
+ })
+ }
}
#[derive(Component)]
@@ -331,7 +369,7 @@ pub fn goto_listener(
pub struct CalculatePathOpts {
pub entity: Entity,
pub start: BlockPos,
- pub goal: Arc<dyn Goal + Send + Sync>,
+ pub goal: Arc<dyn Goal>,
pub successors_fn: SuccessorsFn,
pub world_lock: Arc<RwLock<azalea_world::Instance>>,
pub goto_id_atomic: Arc<AtomicUsize>,