mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
make goto async and clean up some examples
This commit is contained in:
parent
1a0c4e2de9
commit
a9820dfd79
11 changed files with 218 additions and 95 deletions
|
@ -46,19 +46,15 @@ use bevy_ecs::{
|
|||
component::Component,
|
||||
entity::Entity,
|
||||
schedule::{InternedScheduleLabel, IntoSystemConfigs, LogLevel, ScheduleBuildSettings},
|
||||
system::{ResMut, Resource},
|
||||
system::Resource,
|
||||
world::World,
|
||||
};
|
||||
use bevy_time::TimePlugin;
|
||||
use derive_more::Deref;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use simdnbt::owned::NbtCompound;
|
||||
use thiserror::Error;
|
||||
use tokio::{
|
||||
sync::{
|
||||
broadcast,
|
||||
mpsc::{self, error::TrySendError},
|
||||
},
|
||||
sync::mpsc::{self, error::TrySendError},
|
||||
time,
|
||||
};
|
||||
use tracing::{debug, error, info};
|
||||
|
@ -88,6 +84,7 @@ use crate::{
|
|||
raw_connection::RawConnection,
|
||||
respawn::RespawnPlugin,
|
||||
task_pool::TaskPoolPlugin,
|
||||
tick_broadcast::TickBroadcastPlugin,
|
||||
tick_end::TickEndPlugin,
|
||||
};
|
||||
|
||||
|
@ -1006,39 +1003,6 @@ pub async fn tick_run_schedule_loop(run_schedule_sender: mpsc::Sender<()>) {
|
|||
}
|
||||
}
|
||||
|
||||
/// A resource that contains a [`broadcast::Sender`] that will be sent every
|
||||
/// Minecraft tick.
|
||||
///
|
||||
/// This is useful for running code every schedule from async user code.
|
||||
///
|
||||
/// ```
|
||||
/// use azalea_client::TickBroadcast;
|
||||
/// # async fn example(client: azalea_client::Client) {
|
||||
/// let mut receiver = {
|
||||
/// let ecs = client.ecs.lock();
|
||||
/// let tick_broadcast = ecs.resource::<TickBroadcast>();
|
||||
/// tick_broadcast.subscribe()
|
||||
/// };
|
||||
/// while receiver.recv().await.is_ok() {
|
||||
/// // do something
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Resource, Deref)]
|
||||
pub struct TickBroadcast(broadcast::Sender<()>);
|
||||
|
||||
pub fn send_tick_broadcast(tick_broadcast: ResMut<TickBroadcast>) {
|
||||
let _ = tick_broadcast.0.send(());
|
||||
}
|
||||
/// A plugin that makes the [`RanScheduleBroadcast`] resource available.
|
||||
pub struct TickBroadcastPlugin;
|
||||
impl Plugin for TickBroadcastPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(TickBroadcast(broadcast::channel(1).0))
|
||||
.add_systems(GameTick, send_tick_broadcast);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AmbiguityLoggerPlugin;
|
||||
impl Plugin for AmbiguityLoggerPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
|
|
|
@ -24,7 +24,7 @@ pub use account::{Account, AccountOpts};
|
|||
pub use azalea_protocol::common::client_information::ClientInformation;
|
||||
pub use client::{
|
||||
Client, DefaultPlugins, InConfigState, InGameState, JoinError, JoinedClientBundle,
|
||||
LocalPlayerBundle, StartClientOpts, TickBroadcast, start_ecs_runner,
|
||||
LocalPlayerBundle, StartClientOpts, start_ecs_runner,
|
||||
};
|
||||
pub use events::Event;
|
||||
pub use local_player::{GameProfileComponent, Hunger, InstanceHolder, TabList};
|
||||
|
|
|
@ -12,4 +12,5 @@ pub mod packet;
|
|||
pub mod pong;
|
||||
pub mod respawn;
|
||||
pub mod task_pool;
|
||||
pub mod tick_broadcast;
|
||||
pub mod tick_end;
|
||||
|
|
45
azalea-client/src/plugins/tick_broadcast.rs
Normal file
45
azalea-client/src/plugins/tick_broadcast.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use azalea_core::tick::GameTick;
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use derive_more::Deref;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
/// A resource that contains a [`broadcast::Sender`] that will be sent every
|
||||
/// Minecraft tick.
|
||||
///
|
||||
/// This is useful for running code every schedule from async user code.
|
||||
///
|
||||
/// ```
|
||||
/// use azalea_client::TickBroadcast;
|
||||
/// # async fn example(client: azalea_client::Client) {
|
||||
/// let mut receiver = {
|
||||
/// let ecs = client.ecs.lock();
|
||||
/// let tick_broadcast = ecs.resource::<TickBroadcast>();
|
||||
/// tick_broadcast.subscribe()
|
||||
/// };
|
||||
/// while receiver.recv().await.is_ok() {
|
||||
/// // do something
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Resource, Deref)]
|
||||
pub struct TickBroadcast(broadcast::Sender<()>);
|
||||
#[derive(Resource, Deref)]
|
||||
pub struct UpdateBroadcast(broadcast::Sender<()>);
|
||||
|
||||
pub fn send_tick_broadcast(tick_broadcast: ResMut<TickBroadcast>) {
|
||||
let _ = tick_broadcast.0.send(());
|
||||
}
|
||||
pub fn send_update_broadcast(update_broadcast: ResMut<UpdateBroadcast>) {
|
||||
let _ = update_broadcast.0.send(());
|
||||
}
|
||||
/// A plugin that makes the [`RanScheduleBroadcast`] resource available.
|
||||
pub struct TickBroadcastPlugin;
|
||||
impl Plugin for TickBroadcastPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(TickBroadcast(broadcast::channel(1).0))
|
||||
.insert_resource(UpdateBroadcast(broadcast::channel(1).0))
|
||||
.add_systems(GameTick, send_tick_broadcast)
|
||||
.add_systems(Update, send_update_broadcast);
|
||||
}
|
||||
}
|
|
@ -116,7 +116,10 @@ impl Instance {
|
|||
/// Find all the coordinates of a block in the world.
|
||||
///
|
||||
/// This returns an iterator that yields the [`BlockPos`]s of blocks that
|
||||
/// are in the given block states. It's sorted by `x+y+z`.
|
||||
/// are in the given block states.
|
||||
///
|
||||
/// Note that this is sorted by `x+y+z` and not `x^2+y^2+z^2` for
|
||||
/// optimization purposes.
|
||||
pub fn find_blocks<'a>(
|
||||
&'a self,
|
||||
nearest_to: impl Into<BlockPos>,
|
||||
|
|
|
@ -42,9 +42,10 @@ You can just replace these with `azalea` in your code since everything from `aza
|
|||
```rust,no_run
|
||||
//! A bot that logs chat messages sent in the server to the console.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use azalea::prelude::*;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
@ -59,12 +60,15 @@ async fn main() {
|
|||
}
|
||||
|
||||
#[derive(Default, Clone, Component)]
|
||||
pub struct State {}
|
||||
pub struct State {
|
||||
pub messages_received: Arc<Mutex<usize>>
|
||||
}
|
||||
|
||||
async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> {
|
||||
match event {
|
||||
Event::Chat(m) => {
|
||||
println!("{}", m.message().to_ansi());
|
||||
*state.messages_received.lock() += 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use std::sync::Arc;
|
||||
|
||||
use azalea::pathfinder::goals::RadiusGoal;
|
||||
use azalea::{BlockPos, prelude::*};
|
||||
use azalea_inventory::ItemStack;
|
||||
use azalea_inventory::operations::QuickMoveClick;
|
||||
|
@ -21,6 +22,7 @@ async fn main() {
|
|||
|
||||
#[derive(Default, Clone, Component)]
|
||||
struct State {
|
||||
pub is_stealing: Arc<Mutex<bool>>,
|
||||
pub checked_chests: Arc<Mutex<Vec<BlockPos>>>,
|
||||
}
|
||||
|
||||
|
@ -32,43 +34,64 @@ async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> {
|
|||
if m.content() != "go" {
|
||||
return Ok(());
|
||||
}
|
||||
{
|
||||
state.checked_chests.lock().clear();
|
||||
}
|
||||
|
||||
let chest_block = bot
|
||||
.world()
|
||||
.read()
|
||||
.find_block(bot.position(), &azalea::registry::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(chest_block));
|
||||
let Some(chest) = bot.open_container_at(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 ItemStack::Present(item) = slot {
|
||||
if item.kind == azalea::registry::Item::Diamond {
|
||||
println!("clicking slot ^");
|
||||
chest.click(QuickMoveClick::Left { slot: index as u16 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Done");
|
||||
steal(bot, state).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn steal(bot: Client, state: State) -> anyhow::Result<()> {
|
||||
{
|
||||
let mut is_stealing = state.is_stealing.lock();
|
||||
if *is_stealing {
|
||||
bot.chat("Already stealing");
|
||||
return Ok(());
|
||||
}
|
||||
*is_stealing = true;
|
||||
}
|
||||
|
||||
state.checked_chests.lock().clear();
|
||||
|
||||
loop {
|
||||
let chest_block = bot
|
||||
.world()
|
||||
.read()
|
||||
.find_blocks(bot.position(), &azalea::registry::Block::Chest.into())
|
||||
.filter(
|
||||
// filter for chests that haven't been checked
|
||||
|block_pos| !state.checked_chests.lock().contains(&block_pos),
|
||||
)
|
||||
.next();
|
||||
let Some(chest_block) = chest_block else {
|
||||
break;
|
||||
};
|
||||
|
||||
state.checked_chests.lock().push(chest_block);
|
||||
|
||||
bot.goto(RadiusGoal::new(chest_block.center(), 3.)).await;
|
||||
|
||||
let Some(chest) = bot.open_container_at(chest_block).await else {
|
||||
println!("Couldn't open chest at {chest_block:?}");
|
||||
continue;
|
||||
};
|
||||
|
||||
println!("Getting contents of chest at {chest_block:?}");
|
||||
for (index, slot) in chest.contents().unwrap_or_default().iter().enumerate() {
|
||||
println!("Checking slot {index}: {slot:?}");
|
||||
let ItemStack::Present(item) = slot else {
|
||||
continue;
|
||||
};
|
||||
if item.kind == azalea::registry::Item::Diamond {
|
||||
println!("clicking slot ^");
|
||||
chest.click(QuickMoveClick::Left { slot: index as u16 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bot.chat("Done");
|
||||
|
||||
*state.is_stealing.lock() = false;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -28,7 +28,9 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
|
|||
return 0;
|
||||
};
|
||||
source.reply("ok");
|
||||
source.bot.goto(BlockPosGoal(BlockPos::from(position)));
|
||||
source
|
||||
.bot
|
||||
.start_goto(BlockPosGoal(BlockPos::from(position)));
|
||||
1
|
||||
})
|
||||
.then(literal("xz").then(argument("x", integer()).then(
|
||||
|
@ -38,7 +40,7 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
|
|||
let z = get_integer(ctx, "z").unwrap();
|
||||
println!("goto xz {x} {z}");
|
||||
source.reply("ok");
|
||||
source.bot.goto(XZGoal { x, z });
|
||||
source.bot.start_goto(XZGoal { x, z });
|
||||
1
|
||||
}),
|
||||
)))
|
||||
|
@ -52,7 +54,7 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
|
|||
let z = get_integer(ctx, "z").unwrap();
|
||||
println!("goto radius {radius}, position: {x} {y} {z}");
|
||||
source.reply("ok");
|
||||
source.bot.goto(RadiusGoal {
|
||||
source.bot.start_goto(RadiusGoal {
|
||||
pos: BlockPos::new(x, y, z).center(),
|
||||
radius,
|
||||
});
|
||||
|
@ -68,7 +70,7 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
|
|||
let z = get_integer(ctx, "z").unwrap();
|
||||
println!("goto xyz {x} {y} {z}");
|
||||
source.reply("ok");
|
||||
source.bot.goto(BlockPosGoal(BlockPos::new(x, y, z)));
|
||||
source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z)));
|
||||
1
|
||||
}),
|
||||
))),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>,
|
||||
|
|
Loading…
Add table
Reference in a new issue