azalea/
bot.rs

1use std::f64::consts::PI;
2
3use azalea_client::{
4    mining::Mining,
5    tick_broadcast::{TickBroadcast, UpdateBroadcast},
6};
7use azalea_core::{
8    position::{BlockPos, Vec3},
9    tick::GameTick,
10};
11use azalea_entity::{
12    Jumping, LocalEntity, LookDirection, Position, clamp_look_direction,
13    dimensions::EntityDimensions, metadata::Player, update_dimensions,
14};
15use azalea_physics::PhysicsSystems;
16use bevy_app::Update;
17use bevy_ecs::prelude::*;
18use futures_lite::Future;
19use tracing::trace;
20
21use crate::{
22    accept_resource_packs::AcceptResourcePacksPlugin,
23    app::{App, Plugin, PluginGroup, PluginGroupBuilder},
24    auto_respawn::AutoRespawnPlugin,
25    container::ContainerPlugin,
26    ecs::{
27        component::Component,
28        entity::Entity,
29        query::{With, Without},
30        system::{Commands, Query},
31    },
32    pathfinder::PathfinderPlugin,
33};
34
35#[derive(Clone, Default)]
36pub struct BotPlugin;
37impl Plugin for BotPlugin {
38    fn build(&self, app: &mut App) {
39        app.add_message::<LookAtEvent>()
40            .add_message::<JumpEvent>()
41            .add_systems(
42                Update,
43                (
44                    insert_bot,
45                    look_at_listener
46                        .before(clamp_look_direction)
47                        .after(update_dimensions),
48                    jump_listener,
49                ),
50            )
51            .add_systems(
52                GameTick,
53                stop_jumping
54                    .after(PhysicsSystems)
55                    .after(azalea_client::movement::send_player_input_packet),
56            );
57    }
58}
59
60/// A component that clients with [`BotPlugin`] will have.
61///
62/// If you just want to check if an entity is one of our bots, you should use
63/// [`LocalEntity`].
64#[derive(Component, Default)]
65pub struct Bot {
66    jumping_once: bool,
67}
68
69/// Insert the [`Bot`] component for any local players that don't have it.
70#[allow(clippy::type_complexity)]
71fn insert_bot(
72    mut commands: Commands,
73    mut query: Query<Entity, (Without<Bot>, With<LocalEntity>, With<Player>)>,
74) {
75    for entity in &mut query {
76        commands.entity(entity).insert(Bot::default());
77    }
78}
79
80fn stop_jumping(mut query: Query<(&mut Jumping, &mut Bot)>) {
81    for (mut jumping, mut bot) in &mut query {
82        if bot.jumping_once && **jumping {
83            bot.jumping_once = false;
84            **jumping = false;
85        }
86    }
87}
88
89/// A trait that adds a few additional functions to [`Client`] that help with
90/// making bots.
91pub trait BotClientExt {
92    /// Queue a jump for the next tick.
93    fn jump(&self);
94    /// Turn the bot's head to look at the coordinate in the world.
95    ///
96    /// To look at the center of a block, you should call [`BlockPos::center`].
97    fn look_at(&self, pos: Vec3);
98    /// Get a receiver that will receive a message every tick.
99    fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>;
100    /// Get a receiver that will receive a message every ECS Update.
101    fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>;
102    /// Wait for the specified number of game ticks.
103    fn wait_ticks(&self, n: usize) -> impl Future<Output = ()> + Send;
104    /// Wait for the specified number of ECS `Update`s.
105    fn wait_updates(&self, n: usize) -> impl Future<Output = ()> + Send;
106    /// Mine a block.
107    ///
108    /// This won't turn the bot's head towards the block, so if that's necessary
109    /// you'll have to do that yourself with [`look_at`].
110    ///
111    /// [`look_at`]: crate::prelude::BotClientExt::look_at
112    fn mine(&self, position: BlockPos) -> impl Future<Output = ()> + Send;
113}
114
115impl BotClientExt for azalea_client::Client {
116    fn jump(&self) {
117        let mut ecs = self.ecs.lock();
118        ecs.write_message(JumpEvent {
119            entity: self.entity,
120        });
121    }
122
123    fn look_at(&self, position: Vec3) {
124        let mut ecs = self.ecs.lock();
125        ecs.write_message(LookAtEvent {
126            entity: self.entity,
127            position,
128        });
129    }
130
131    /// Returns a Receiver that receives a message every game tick.
132    ///
133    /// This is useful if you want to efficiently loop until a certain condition
134    /// is met.
135    ///
136    /// ```
137    /// # use azalea::prelude::*;
138    /// # use azalea::container::WaitingForInventoryOpen;
139    /// # async fn example(bot: &mut azalea::Client) {
140    /// let mut ticks = bot.get_tick_broadcaster();
141    /// while ticks.recv().await.is_ok() {
142    ///     let ecs = bot.ecs.lock();
143    ///     if ecs.get::<WaitingForInventoryOpen>(bot.entity).is_none() {
144    ///         break;
145    ///     }
146    /// }
147    /// # }
148    /// ```
149    fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()> {
150        let ecs = self.ecs.lock();
151        let tick_broadcast = ecs.resource::<TickBroadcast>();
152        tick_broadcast.subscribe()
153    }
154
155    /// Returns a Receiver that receives a message every ECS Update.
156    ///
157    /// ECS Updates happen at least at the frequency of game ticks, usually
158    /// faster.
159    ///
160    /// This is useful if you're sending an ECS event and want to make sure it's
161    /// been handled before continuing.
162    fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()> {
163        let ecs = self.ecs.lock();
164        let update_broadcast = ecs.resource::<UpdateBroadcast>();
165        update_broadcast.subscribe()
166    }
167
168    /// Wait for the specified number of ticks using
169    /// [`Self::get_tick_broadcaster`].
170    ///
171    /// If you're going to run this in a loop, you may want to use that function
172    /// instead and use the `Receiver` from it to avoid accidentally skipping
173    /// ticks and having to wait longer.
174    async fn wait_ticks(&self, n: usize) {
175        let mut receiver = self.get_tick_broadcaster();
176        for _ in 0..n {
177            let _ = receiver.recv().await;
178        }
179    }
180    /// Waits for the specified number of ECS `Update`s using
181    /// [`Self::get_update_broadcaster`].
182    ///
183    /// These are basically equivalent to frames because even though we have no
184    /// rendering, some game mechanics depend on frames.
185    ///
186    /// If you're going to run this in a loop, you may want to use that function
187    /// instead and use the `Receiver` from it to avoid accidentally skipping
188    /// ticks and having to wait longer.
189    async fn wait_updates(&self, n: usize) {
190        let mut receiver = self.get_update_broadcaster();
191        for _ in 0..n {
192            let _ = receiver.recv().await;
193        }
194    }
195
196    async fn mine(&self, position: BlockPos) {
197        self.start_mining(position);
198
199        let mut receiver = self.get_tick_broadcaster();
200        while receiver.recv().await.is_ok() {
201            let ecs = self.ecs.lock();
202            if ecs.get::<Mining>(self.entity).is_none() {
203                break;
204            }
205        }
206    }
207}
208
209/// Event to jump once.
210#[derive(Message)]
211pub struct JumpEvent {
212    pub entity: Entity,
213}
214
215pub fn jump_listener(
216    mut query: Query<(&mut Jumping, &mut Bot)>,
217    mut events: MessageReader<JumpEvent>,
218) {
219    for event in events.read() {
220        if let Ok((mut jumping, mut bot)) = query.get_mut(event.entity) {
221            **jumping = true;
222            bot.jumping_once = true;
223        }
224    }
225}
226
227/// Make an entity look towards a certain position in the world.
228#[derive(Message)]
229pub struct LookAtEvent {
230    pub entity: Entity,
231    /// The position we want the entity to be looking at.
232    pub position: Vec3,
233}
234fn look_at_listener(
235    mut events: MessageReader<LookAtEvent>,
236    mut query: Query<(&Position, &EntityDimensions, &mut LookDirection)>,
237) {
238    for event in events.read() {
239        if let Ok((position, dimensions, mut look_direction)) = query.get_mut(event.entity) {
240            let new_look_direction =
241                direction_looking_at(position.up(dimensions.eye_height.into()), event.position);
242
243            trace!("look at {} (currently at {})", event.position, **position);
244            look_direction.update(new_look_direction);
245        }
246    }
247}
248
249/// Return the look direction that would make a client at `current` be
250/// looking at `target`.
251pub fn direction_looking_at(current: Vec3, target: Vec3) -> LookDirection {
252    // borrowed from mineflayer's Bot.lookAt because i didn't want to do math
253    let delta = target - current;
254    let y_rot = (PI - f64::atan2(-delta.x, -delta.z)) * (180.0 / PI);
255    let ground_distance = f64::sqrt(delta.x * delta.x + delta.z * delta.z);
256    let x_rot = f64::atan2(delta.y, ground_distance) * -(180.0 / PI);
257
258    LookDirection::new(y_rot as f32, x_rot as f32)
259}
260
261/// A [`PluginGroup`] for the plugins that add extra bot functionality to the
262/// client.
263pub struct DefaultBotPlugins;
264
265impl PluginGroup for DefaultBotPlugins {
266    fn build(self) -> PluginGroupBuilder {
267        PluginGroupBuilder::start::<Self>()
268            .add(BotPlugin)
269            .add(PathfinderPlugin)
270            .add(ContainerPlugin)
271            .add(AutoRespawnPlugin)
272            .add(AcceptResourcePacksPlugin)
273    }
274}