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