use crate::{ State, commands::CommandSource, http::serve, lua::{self, direction::Direction, player::Player, vec3::Vec3}, particle, }; use azalea::{prelude::*, protocol::packets::game::ClientboundGamePacket}; use hyper::{server::conn::http1, service::service_fn}; use hyper_util::rt::TokioIo; use log::{debug, error, info, trace}; use mlua::{Function, IntoLuaMulti, Table}; use ncr::utils::trim_header; use std::process::exit; use tokio::net::TcpListener; #[allow(clippy::too_many_lines)] pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow::Result<()> { match event { Event::AddPlayer(player_info) => { call_listeners(&state, "add_player", Player::from(player_info)).await; } Event::Chat(message) => { let globals = state.lua.globals(); let (sender, mut content) = message.split_sender_and_content(); let formatted_message = message.message(); info!("{}", formatted_message.to_ansi()); if let Some(sender) = sender { let mut ncr_options = None; if let Ok(options) = globals.get::<Table>("NcrOptions") && let Ok(decrypt) = globals.get::<Function>("ncr_decrypt") && let Some(plaintext) = decrypt .call::<String>((options.clone(), content.clone())) .ok() .as_deref() .and_then(|s| trim_header(s).ok()) { ncr_options = Some(options); plaintext.clone_into(&mut content); info!("decrypted message from {sender}: {content}"); } if message.is_whisper() && globals.get::<Vec<String>>("Owners")?.contains(&sender) { if let Err(error) = state.commands.execute( content, CommandSource { client: client.clone(), message: message.clone(), state: state.clone(), ncr_options: ncr_options.clone(), } .into(), ) { CommandSource { client, message, state: state.clone(), ncr_options, } .reply(&format!("{error:?}")); } } } call_listeners(&state, "chat", formatted_message.to_string()).await; } Event::Death(packet) => { if let Some(packet) = packet { let table = state.lua.create_table()?; table.set("message", packet.message.to_string())?; table.set("player_id", packet.player_id.0)?; call_listeners(&state, "death", table).await; } else { call_listeners(&state, "death", ()).await; } } Event::Disconnect(message) => { call_listeners(&state, "disconnect", message.map(|m| m.to_string())).await; exit(0) } Event::KeepAlive(id) => call_listeners(&state, "keep_alive", id).await, Event::Login => call_listeners(&state, "login", ()).await, Event::RemovePlayer(player_info) => { call_listeners(&state, "remove_player", Player::from(player_info)).await; } Event::Tick => call_listeners(&state, "tick", ()).await, Event::UpdatePlayer(player_info) => { call_listeners(&state, "update_player", Player::from(player_info)).await; } Event::Packet(packet) => match packet.as_ref() { ClientboundGamePacket::AddEntity(packet) => { let table = state.lua.create_table()?; table.set("id", packet.id.0)?; table.set("uuid", packet.uuid.to_string())?; table.set("kind", packet.entity_type.to_string())?; table.set("position", Vec3::from(packet.position))?; table.set( "direction", Direction { y: f32::from(packet.y_rot) / (256.0 / 360.0), x: f32::from(packet.x_rot) / (256.0 / 360.0), }, )?; table.set("data", packet.data)?; call_listeners(&state, "add_entity", table).await; } ClientboundGamePacket::LevelParticles(packet) => { let table = state.lua.create_table()?; table.set("position", Vec3::from(packet.pos))?; table.set("count", packet.count)?; table.set("kind", particle::to_kind(&packet.particle) as u8)?; call_listeners(&state, "level_particles", table).await; } ClientboundGamePacket::RemoveEntities(packet) => { call_listeners( &state, "remove_entities", packet.entity_ids.iter().map(|id| id.0).collect::<Vec<_>>(), ) .await; } ClientboundGamePacket::SetHealth(packet) => { let table = state.lua.create_table()?; table.set("food", packet.food)?; table.set("health", packet.health)?; table.set("saturation", packet.saturation)?; call_listeners(&state, "set_health", table).await; } ClientboundGamePacket::SetPassengers(packet) => { let table = state.lua.create_table()?; table.set("vehicle", packet.vehicle)?; table.set("passengers", &*packet.passengers)?; call_listeners(&state, "set_passengers", table).await; } _ => (), }, Event::Init => { debug!("received initialize event"); state.lua.globals().set( "client", lua::client::Client { inner: Some(client), }, )?; call_listeners(&state, "init", ()).await; let Some(address) = state.http_address else { return Ok(()); }; let listener = TcpListener::bind(address).await.map_err(|error| { error!("failed to listen on {address}: {error:?}"); error })?; debug!("http server listening on {address}"); loop { let (stream, peer) = listener.accept().await?; trace!("http server got connection from {peer}"); let conn_state = state.clone(); let service = service_fn(move |request| { let request_state = conn_state.clone(); async move { serve(request, request_state).await } }); tokio::spawn(async move { if let Err(error) = http1::Builder::new() .serve_connection(TokioIo::new(stream), service) .await { error!("failed to serve connection: {error:?}"); } }); } } } Ok(()) } async fn call_listeners<T: Clone + IntoLuaMulti + Send + 'static>( state: &State, event_type: &'static str, data: T, ) { if let Some(listeners) = state.event_listeners.read().await.get(event_type).cloned() { for (id, callback) in listeners { let data = data.clone(); tokio::spawn(async move { if let Err(error) = callback.call_async::<()>(data).await { error!("failed to call lua event listener {id} for {event_type}: {error:?}"); } }); } } }