141 lines
4.0 KiB
Rust

#![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},
path::PathBuf,
sync::Arc,
};
#[cfg(feature = "mimalloc")]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
type ListenerMap = Arc<RwLock<HashMap<String, Vec<(String, Function)>>>>;
#[derive(Default, Clone, Component)]
pub struct State {
lua: Arc<Lua>,
event_listeners: ListenerMap,
commands: Arc<CommandDispatcher<Mutex<CommandSource>>>,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
#[cfg(feature = "console-subscriber")]
console_subscriber::init();
let args = Arguments::parse();
let script_path = args.script.unwrap_or(PathBuf::from("errornowatcher.lua"));
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", &*script_path)?;
lua.load(
read_to_string(&script_path)
.with_context(|| format!("failed to read {}", script_path.display()))?,
)
.exec()?;
if let Some(code) = args.exec {
lua.load(code).exec()?;
}
let server = globals
.get::<String>("Server")
.expect("Server should be in lua globals");
let username = globals
.get::<String>("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::<LogPlugin>()
} 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::<Table>("ReplayRecordingOptions")
&& let Ok(path) = options.get::<String>("path")
{
Some(Recorder::new(
path,
server.clone(),
options
.get::<bool>("ignore_compression")
.unwrap_or_default(),
)?)
} else {
None
},
)),
};
let Err(error) = ClientBuilder::new_without_plugins()
.add_plugins(default_plugins)
.add_plugins(record_plugin)
.add_plugins(DefaultBotPlugins)
.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.as_ref(),
)
.await;
eprintln!("{error}");
Ok(())
}