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