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#[derive(Default, Component)]
57pub struct Bot {
58 jumping_once: bool,
59}
60
61#[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 fn jump(&self);
84 fn look_at(&self, pos: Vec3);
86 fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>;
88 fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>;
90 fn wait_one_tick(&self) -> impl Future<Output = ()> + Send;
92 fn wait_one_update(&self) -> impl Future<Output = ()> + Send;
94 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 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 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 async fn wait_one_tick(&self) {
159 let mut receiver = self.get_tick_broadcaster();
160 let _ = receiver.recv().await;
162 }
163 async fn wait_one_update(&self) {
168 let mut receiver = self.get_update_broadcaster();
169 let _ = receiver.recv().await;
171 }
172
173 async fn mine(&self, position: BlockPos) {
174 self.start_mining(position);
175 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#[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#[derive(Event)]
210pub struct LookAtEvent {
211 pub entity: Entity,
212 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
232pub fn direction_looking_at(current: &Vec3, target: &Vec3) -> LookDirection {
235 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 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
251pub 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}