1mod commands;
26pub mod killaura;
27pub mod mspt;
28
29use std::{env, process, sync::Arc, thread, time::Duration};
30
31use azalea::{
32 ClientInformation, EntityRef,
33 brigadier::command_dispatcher::CommandDispatcher,
34 ecs::prelude::*,
35 pathfinder::{
36 PathfinderOpts,
37 debug::PathfinderDebugParticles,
38 execute::simulation::SimulationPathfinderExecutionPlugin,
39 goals::{Goal, RadiusGoal},
40 },
41 prelude::*,
42 swarm::prelude::*,
43};
44use commands::{CommandSource, register_commands};
45use parking_lot::Mutex;
46
47#[tokio::main]
48async fn main() -> AppExit {
49 let args = parse_args();
50
51 thread::spawn(deadlock_detection_thread);
52
53 let join_address = args.server.clone();
54
55 let mut builder = SwarmBuilder::new()
56 .set_handler(handle)
57 .set_swarm_handler(swarm_handle);
58
59 if args.simulation_pathfinder {
60 builder = builder.add_plugins(SimulationPathfinderExecutionPlugin);
61 }
62
63 for username_or_email in &args.accounts {
64 let account = if username_or_email.contains('@') {
65 Account::microsoft(username_or_email).await.unwrap()
66 } else {
67 Account::offline(username_or_email)
68 };
69
70 builder = builder.add_account_with_state(account, State::new());
71 }
72
73 let mut commands = CommandDispatcher::new();
74 register_commands(&mut commands);
75
76 builder
77 .join_delay(Duration::from_millis(100))
78 .set_swarm_state(SwarmState {
79 args: args.into(),
80 commands: commands.into(),
81 })
82 .start(join_address)
84 .await
85}
86
87fn deadlock_detection_thread() {
93 loop {
94 thread::sleep(Duration::from_secs(10));
95 let deadlocks = parking_lot::deadlock::check_deadlock();
96 if deadlocks.is_empty() {
97 continue;
98 }
99
100 println!("{} deadlocks detected", deadlocks.len());
101 for (i, threads) in deadlocks.iter().enumerate() {
102 println!("Deadlock #{i}");
103 for t in threads {
104 println!("Thread Id {:#?}", t.thread_id());
105 println!("{:#?}", t.backtrace());
106 }
107 }
108 }
109}
110
111#[derive(Clone, Component, Default)]
112pub struct State {
113 pub killaura: bool,
114 pub following_entity: Arc<Mutex<Option<EntityRef>>>,
115}
116
117impl State {
118 fn new() -> Self {
119 Self {
120 killaura: false,
121 following_entity: Default::default(),
122 }
123 }
124}
125
126#[derive(Clone, Default, Resource)]
127struct SwarmState {
128 pub args: Arc<Args>,
129 pub commands: Arc<CommandDispatcher<Mutex<CommandSource>>>,
130}
131
132async fn handle(bot: Client, event: azalea::Event, state: State) -> eyre::Result<()> {
133 let swarm = bot.resource::<SwarmState>();
134
135 match event {
136 azalea::Event::Init => {
137 bot.set_client_information(ClientInformation {
138 view_distance: 32,
139 ..Default::default()
140 });
141 if swarm.args.pathfinder_debug_particles {
142 bot.ecs
143 .write()
144 .entity_mut(bot.entity)
145 .insert(PathfinderDebugParticles);
146 }
147 }
148 azalea::Event::Chat(chat) => {
149 let (Some(username), content) = chat.split_sender_and_content() else {
150 return Ok(());
151 };
152 if username != swarm.args.owner_username {
153 return Ok(());
154 }
155
156 println!("{:?}", chat.message());
157
158 let command = if chat.is_whisper() {
159 Some(content)
160 } else {
161 content.strip_prefix('!').map(|s| s.to_owned())
162 };
163 if let Some(command) = command {
164 match swarm.commands.execute(
165 command,
166 Mutex::new(CommandSource {
167 bot: bot.clone(),
168 chat: chat.clone(),
169 state: state.clone(),
170 }),
171 ) {
172 Ok(_) => {}
173 Err(err) => {
174 eprintln!("{err:?}");
175 let command_source = CommandSource {
176 bot,
177 chat: chat.clone(),
178 state: state.clone(),
179 };
180 command_source.reply(format!("{err:?}"));
181 }
182 }
183 }
184 }
185 azalea::Event::Tick => {
186 killaura::tick(bot.clone(), state.clone())?;
187
188 if bot.ticks_connected().is_multiple_of(5) {
189 if let Some(following) = &*state.following_entity.lock()
190 && following.is_alive()
191 {
192 let goal = RadiusGoal::new(following.position(), 3.);
193 if bot.is_calculating_path() {
194 } else if !goal.success(bot.position().into()) || bot.is_executing_path() {
196 bot.start_goto_with_opts(
197 goal,
198 PathfinderOpts::new()
199 .retry_on_no_path(false)
200 .max_timeout(Duration::from_secs(1)),
201 );
202 } else {
203 following.look_at();
204 }
205 }
206 }
207 }
208 azalea::Event::Login => {
209 println!("Got login event")
210 }
211 _ => {}
212 }
213
214 Ok(())
215}
216async fn swarm_handle(_swarm: Swarm, event: SwarmEvent, _state: SwarmState) -> eyre::Result<()> {
217 match &event {
218 SwarmEvent::Disconnect(account, _join_opts) => {
219 println!("bot got kicked! {}", account.username());
220 }
221 SwarmEvent::Chat(chat) => {
222 if chat.message().to_string() == "The particle was not visible for anybody" {
223 return Ok(());
224 }
225 println!("{}", chat.message().to_ansi());
226 }
227 _ => {}
228 }
229
230 Ok(())
231}
232
233#[derive(Clone, Debug, Default)]
234pub struct Args {
235 pub owner_username: String,
236 pub accounts: Vec<String>,
237 pub server: String,
238 pub pathfinder_debug_particles: bool,
239 pub simulation_pathfinder: bool,
240}
241
242fn parse_args() -> Args {
243 let mut owner_username = "admin".to_owned();
244 let mut accounts = Vec::new();
245 let mut server = "localhost".to_owned();
246 let mut pathfinder_debug_particles = false;
247 let mut simulation_pathfinder = false;
248
249 let mut args = env::args().skip(1);
250 while let Some(arg) = args.next() {
251 match arg.as_str() {
252 "--owner" | "-O" => {
253 owner_username = args.next().expect("Missing owner username");
254 }
255 "--account" | "-A" => {
256 for account in args.next().expect("Missing account").split(',') {
257 accounts.push(account.to_string());
258 }
259 }
260 "--server" | "-S" => {
261 server = args.next().expect("Missing server address");
262 }
263 "--pathfinder-debug-particles" | "-P" => {
264 pathfinder_debug_particles = true;
265 }
266 "--simulation-pathfinder" => {
267 simulation_pathfinder = true;
268 }
269 _ => {
270 eprintln!("Unknown argument: {arg}");
271 process::exit(1);
272 }
273 }
274 }
275
276 if accounts.is_empty() {
277 accounts.push("azalea".to_owned());
278 }
279
280 Args {
281 owner_username,
282 accounts,
283 server,
284 pathfinder_debug_particles,
285 simulation_pathfinder,
286 }
287}