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