mod commands;
pub mod killaura;
pub mod mspt;
use std::{env, process, sync::Arc, thread, time::Duration};
use azalea::{
ClientInformation, EntityRef,
brigadier::command_dispatcher::CommandDispatcher,
ecs::prelude::*,
pathfinder::{
PathfinderOpts,
debug::PathfinderDebugParticles,
execute::simulation::SimulationPathfinderExecutionPlugin,
goals::{Goal, RadiusGoal},
},
prelude::*,
swarm::prelude::*,
};
use commands::{CommandSource, register_commands};
use parking_lot::Mutex;
#[tokio::main]
async fn main() -> AppExit {
let args = parse_args();
thread::spawn(deadlock_detection_thread);
let join_address = args.server.clone();
let mut builder = SwarmBuilder::new()
.set_handler(handle)
.set_swarm_handler(swarm_handle);
if args.simulation_pathfinder {
builder = builder.add_plugins(SimulationPathfinderExecutionPlugin);
}
for username_or_email in &args.accounts {
let account = if username_or_email.contains('@') {
Account::microsoft(username_or_email).await.unwrap()
} else {
Account::offline(username_or_email)
};
builder = builder.add_account_with_state(account, State::new());
}
let mut commands = CommandDispatcher::new();
register_commands(&mut commands);
builder
.join_delay(Duration::from_millis(100))
.set_swarm_state(SwarmState {
args: args.into(),
commands: commands.into(),
})
.start(join_address)
.await
}
fn deadlock_detection_thread() {
loop {
thread::sleep(Duration::from_secs(10));
let deadlocks = parking_lot::deadlock::check_deadlock();
if deadlocks.is_empty() {
continue;
}
println!("{} deadlocks detected", deadlocks.len());
for (i, threads) in deadlocks.iter().enumerate() {
println!("Deadlock #{i}");
for t in threads {
println!("Thread Id {:#?}", t.thread_id());
println!("{:#?}", t.backtrace());
}
}
}
}
#[derive(Clone, Component, Default)]
pub struct State {
pub killaura: bool,
pub following_entity: Arc<Mutex<Option<EntityRef>>>,
}
impl State {
fn new() -> Self {
Self {
killaura: false,
following_entity: Default::default(),
}
}
}
#[derive(Clone, Default, Resource)]
struct SwarmState {
pub args: Arc<Args>,
pub commands: Arc<CommandDispatcher<Mutex<CommandSource>>>,
}
async fn handle(bot: Client, event: azalea::Event, state: State) -> eyre::Result<()> {
let swarm = bot.resource::<SwarmState>();
match event {
azalea::Event::Init => {
bot.set_client_information(ClientInformation {
view_distance: 32,
..Default::default()
});
if swarm.args.pathfinder_debug_particles {
bot.ecs
.write()
.entity_mut(bot.entity)
.insert(PathfinderDebugParticles);
}
}
azalea::Event::Chat(chat) => {
let (Some(username), content) = chat.split_sender_and_content() else {
return Ok(());
};
if username != swarm.args.owner_username {
return Ok(());
}
println!("{:?}", chat.message());
let command = if chat.is_whisper() {
Some(content)
} else {
content.strip_prefix('!').map(|s| s.to_owned())
};
if let Some(command) = command {
match swarm.commands.execute(
command,
Mutex::new(CommandSource {
bot: bot.clone(),
chat: chat.clone(),
state: state.clone(),
}),
) {
Ok(_) => {}
Err(err) => {
eprintln!("{err:?}");
let command_source = CommandSource {
bot,
chat: chat.clone(),
state: state.clone(),
};
command_source.reply(format!("{err:?}"));
}
}
}
}
azalea::Event::Tick => {
killaura::tick(bot.clone(), state.clone())?;
if bot.ticks_connected().is_multiple_of(5) {
if let Some(following) = &*state.following_entity.lock()
&& following.is_alive()
{
let goal = RadiusGoal::new(following.position(), 3.);
if bot.is_calculating_path() {
} else if !goal.success(bot.position().into()) || bot.is_executing_path() {
bot.start_goto_with_opts(
goal,
PathfinderOpts::new()
.retry_on_no_path(false)
.max_timeout(Duration::from_secs(1)),
);
} else {
following.look_at();
}
}
}
}
azalea::Event::Login => {
println!("Got login event")
}
_ => {}
}
Ok(())
}
async fn swarm_handle(_swarm: Swarm, event: SwarmEvent, _state: SwarmState) -> eyre::Result<()> {
match &event {
SwarmEvent::Disconnect(account, _join_opts) => {
println!("bot got kicked! {}", account.username());
}
SwarmEvent::Chat(chat) => {
if chat.message().to_string() == "The particle was not visible for anybody" {
return Ok(());
}
println!("{}", chat.message().to_ansi());
}
_ => {}
}
Ok(())
}
#[derive(Clone, Debug, Default)]
pub struct Args {
pub owner_username: String,
pub accounts: Vec<String>,
pub server: String,
pub pathfinder_debug_particles: bool,
pub simulation_pathfinder: bool,
}
fn parse_args() -> Args {
let mut owner_username = "admin".to_owned();
let mut accounts = Vec::new();
let mut server = "localhost".to_owned();
let mut pathfinder_debug_particles = false;
let mut simulation_pathfinder = false;
let mut args = env::args().skip(1);
while let Some(arg) = args.next() {
match arg.as_str() {
"--owner" | "-O" => {
owner_username = args.next().expect("Missing owner username");
}
"--account" | "-A" => {
for account in args.next().expect("Missing account").split(',') {
accounts.push(account.to_string());
}
}
"--server" | "-S" => {
server = args.next().expect("Missing server address");
}
"--pathfinder-debug-particles" | "-P" => {
pathfinder_debug_particles = true;
}
"--simulation-pathfinder" => {
simulation_pathfinder = true;
}
_ => {
eprintln!("Unknown argument: {arg}");
process::exit(1);
}
}
}
if accounts.is_empty() {
accounts.push("azalea".to_owned());
}
Args {
owner_username,
accounts,
server,
pathfinder_debug_particles,
simulation_pathfinder,
}
}