azalea/
bot.rs

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