azalea/
bot.rs

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