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