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::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
47 .before(clamp_look_direction)
48 .after(update_dimensions),
49 jump_listener,
50 ),
51 )
52 .add_systems(
53 GameTick,
54 stop_jumping
55 .after(PhysicsSet)
56 .after(azalea_client::movement::send_player_input_packet),
57 );
58 }
59}
60
61#[derive(Default, Component)]
64pub struct Bot {
65 jumping_once: bool,
66}
67
68#[allow(clippy::type_complexity)]
70fn insert_bot(
71 mut commands: Commands,
72 mut query: Query<Entity, (Without<Bot>, With<LocalEntity>, With<Player>)>,
73) {
74 for entity in &mut query {
75 commands.entity(entity).insert(Bot::default());
76 }
77}
78
79fn stop_jumping(mut query: Query<(&mut Jumping, &mut Bot)>) {
80 for (mut jumping, mut bot) in &mut query {
81 if bot.jumping_once && **jumping {
82 bot.jumping_once = false;
83 **jumping = false;
84 }
85 }
86}
87
88pub trait BotClientExt {
89 fn jump(&self);
91 fn look_at(&self, pos: Vec3);
93 fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>;
95 fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>;
97 fn wait_ticks(&self, n: usize) -> impl Future<Output = ()> + Send;
99 fn wait_updates(&self, n: usize) -> impl Future<Output = ()> + Send;
101 fn mine(&self, position: BlockPos) -> impl Future<Output = ()> + Send;
106}
107
108impl BotClientExt for azalea_client::Client {
109 fn jump(&self) {
110 let mut ecs = self.ecs.lock();
111 ecs.send_event(JumpEvent {
112 entity: self.entity,
113 });
114 }
115
116 fn look_at(&self, position: Vec3) {
117 let mut ecs = self.ecs.lock();
118 ecs.send_event(LookAtEvent {
119 entity: self.entity,
120 position,
121 });
122 }
123
124 fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()> {
143 let ecs = self.ecs.lock();
144 let tick_broadcast = ecs.resource::<TickBroadcast>();
145 tick_broadcast.subscribe()
146 }
147
148 fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()> {
156 let ecs = self.ecs.lock();
157 let update_broadcast = ecs.resource::<UpdateBroadcast>();
158 update_broadcast.subscribe()
159 }
160
161 async fn wait_ticks(&self, n: usize) {
168 let mut receiver = self.get_tick_broadcaster();
169 for _ in 0..n {
170 let _ = receiver.recv().await;
171 }
172 }
173 async fn wait_updates(&self, n: usize) {
183 let mut receiver = self.get_update_broadcaster();
184 for _ in 0..n {
185 let _ = receiver.recv().await;
186 }
187 }
188
189 async fn mine(&self, position: BlockPos) {
190 self.start_mining(position);
191
192 let mut receiver = self.get_tick_broadcaster();
193 while receiver.recv().await.is_ok() {
194 let ecs = self.ecs.lock();
195 if ecs.get::<Mining>(self.entity).is_none() {
196 break;
197 }
198 }
199 }
200}
201
202#[derive(Event)]
204pub struct JumpEvent {
205 pub entity: Entity,
206}
207
208pub fn jump_listener(
209 mut query: Query<(&mut Jumping, &mut Bot)>,
210 mut events: EventReader<JumpEvent>,
211) {
212 for event in events.read() {
213 if let Ok((mut jumping, mut bot)) = query.get_mut(event.entity) {
214 **jumping = true;
215 bot.jumping_once = true;
216 }
217 }
218}
219
220#[derive(Event)]
222pub struct LookAtEvent {
223 pub entity: Entity,
224 pub position: Vec3,
226}
227fn look_at_listener(
228 mut events: EventReader<LookAtEvent>,
229 mut query: Query<(&Position, &EntityDimensions, &mut LookDirection)>,
230) {
231 for event in events.read() {
232 if let Ok((position, dimensions, mut look_direction)) = query.get_mut(event.entity) {
233 let new_look_direction =
234 direction_looking_at(position.up(dimensions.eye_height.into()), event.position);
235
236 trace!("look at {} (currently at {})", event.position, **position);
237 look_direction.update(new_look_direction);
238 }
239 }
240}
241
242pub fn direction_looking_at(current: Vec3, target: Vec3) -> LookDirection {
245 let delta = target - current;
247 let y_rot = (PI - f64::atan2(-delta.x, -delta.z)) * (180.0 / PI);
248 let ground_distance = f64::sqrt(delta.x * delta.x + delta.z * delta.z);
249 let x_rot = f64::atan2(delta.y, ground_distance) * -(180.0 / PI);
250
251 LookDirection::new(y_rot as f32, x_rot as f32)
252}
253
254pub struct DefaultBotPlugins;
257
258impl PluginGroup for DefaultBotPlugins {
259 fn build(self) -> PluginGroupBuilder {
260 PluginGroupBuilder::start::<Self>()
261 .add(BotPlugin)
262 .add(PathfinderPlugin)
263 .add(ContainerPlugin)
264 .add(AutoRespawnPlugin)
265 .add(AcceptResourcePacksPlugin)
266 }
267}