#![feature(let_chains)] mod arguments; mod build_info; mod commands; mod events; mod http; mod lua; mod particle; mod replay; use anyhow::Context; use arguments::Arguments; use azalea::{ DefaultBotPlugins, DefaultPlugins, brigadier::prelude::CommandDispatcher, prelude::*, }; use bevy_app::PluginGroup; use bevy_log::{ LogPlugin, tracing_subscriber::{Layer, fmt::layer}, }; use clap::Parser; use commands::{CommandSource, register}; use futures::lock::Mutex; use futures_locks::RwLock; use mlua::{Function, Lua, Table}; use replay::{plugin::RecordPlugin, recorder::Recorder}; use std::{ collections::HashMap, env, fs::{OpenOptions, read_to_string}, sync::Arc, }; #[cfg(feature = "mimalloc")] #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; type ListenerMap = Arc>>>; #[derive(Default, Clone, Component)] pub struct State { lua: Arc, event_listeners: ListenerMap, commands: Arc>>, } #[tokio::main] async fn main() -> anyhow::Result<()> { #[cfg(feature = "console-subscriber")] console_subscriber::init(); let args = Arguments::parse(); let event_listeners = Arc::new(RwLock::new(HashMap::new())); let lua = unsafe { Lua::unsafe_new() }; let globals = lua.globals(); lua::register_globals(&lua, &globals, event_listeners.clone())?; globals.set("SCRIPT_PATH", &*args.script)?; lua.load( read_to_string(&args.script) .with_context(|| format!("failed to read {}", args.script.display()))?, ) .exec()?; if let Some(code) = args.exec { lua.load(code).exec()?; } let server = globals .get::("Server") .expect("Server should be in lua globals"); let username = globals .get::("Username") .expect("Username should be in lua globals"); let mut commands = CommandDispatcher::new(); register(&mut commands); let default_plugins = if cfg!(feature = "console-subscriber") { DefaultPlugins.build().disable::() } else { DefaultPlugins.set(LogPlugin { custom_layer: |_| { env::var("LOG_FILE").ok().map(|path| { layer() .with_writer( OpenOptions::new() .append(true) .create(true) .open(&path) .expect(&(path + " should be accessible")), ) .boxed() }) }, ..Default::default() }) }; let record_plugin = RecordPlugin { recorder: Arc::new(parking_lot::Mutex::new( if let Ok(options) = globals.get::("ReplayRecordingOptions") && let Ok(path) = options.get::("path") { Some(Recorder::new( path, server.clone(), options .get::("ignore_compression") .unwrap_or_default(), )?) } else { None }, )), }; let Err(error) = ClientBuilder::new_without_plugins() .add_plugins(DefaultBotPlugins) .add_plugins(default_plugins) .add_plugins(record_plugin) .set_handler(events::handle_event) .set_state(State { lua: Arc::new(lua), event_listeners, commands: Arc::new(commands), }) .start( if username.contains('@') { Account::microsoft(&username).await? } else { Account::offline(&username) }, server, ) .await; eprintln!("{error}"); Ok(()) }