use crate::{
State,
commands::CommandSource,
http::serve,
lua::{client, direction::Direction, player::Player, vec3::Vec3},
particle,
replay::Recorder,
};
use anyhow::Context;
use azalea::{
brigadier::exceptions::BuiltInExceptions::DispatcherUnknownCommand, 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::{Error, Function, IntoLuaMulti, Table};
use ncr::utils::trim_header;
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 uuid = message.uuid().map(|uuid| uuid.to_string());
let is_whisper = message.is_whisper();
let text = message.message();
let ansi_text = text.to_ansi();
info!("{ansi_text}");
let mut is_encrypted = false;
if let Some(ref sender) = sender {
let mut ncr_options = None;
if let Ok(options) = globals.get::
("NcrOptions")
&& let Ok(decrypt) = globals.get::("ncr_decrypt")
&& let Some(plaintext) = decrypt
.call::((options.clone(), content.clone()))
.ok()
.as_deref()
.and_then(|s| trim_header(s).ok())
{
is_encrypted = true;
ncr_options = Some(options);
plaintext.clone_into(&mut content);
info!("decrypted message from {sender}: {content}");
}
if is_whisper
&& globals
.get::>("Owners")
.unwrap_or_default()
.contains(sender)
&& let Err(error) = state.commands.execute(
content.clone(),
CommandSource {
client: client.clone(),
message: message.clone(),
state: state.clone(),
ncr_options: ncr_options.clone(),
}
.into(),
)
&& error.type_ != DispatcherUnknownCommand
{
CommandSource {
client,
message,
state: state.clone(),
ncr_options,
}
.reply(&format!("{error:?}"));
}
}
let table = state.lua.create_table()?;
table.set("text", text.to_string())?;
table.set("ansi_text", ansi_text)?;
table.set("sender", sender)?;
table.set("content", content)?;
table.set("uuid", uuid)?;
table.set("is_whisper", is_whisper)?;
table.set("is_encrypted", is_encrypted)?;
call_listeners(&state, "chat", table).await;
}
Event::Death(packet) => {
if let Some(packet) = packet {
let message_table = state.lua.create_table()?;
message_table.set("text", packet.message.to_string())?;
message_table.set("ansi_text", packet.message.to_ansi())?;
let table = state.lua.create_table()?;
table.set("message", message_table)?;
table.set("player_id", packet.player_id.0)?;
call_listeners(&state, "death", table).await;
} else {
call_listeners(&state, "death", ()).await;
}
}
Event::Disconnect(message) => {
if let Some(message) = message {
let table = state.lua.create_table()?;
table.set("text", message.to_string())?;
table.set("ansi_text", message.to_ansi())?;
call_listeners(&state, "disconnect", table).await;
} else {
call_listeners(&state, "disconnect", ()).await;
}
}
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::>(),
)
.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");
let globals = state.lua.globals();
let lua_ecs = client.ecs.clone();
globals.set(
"finish_replay_recording",
state.lua.create_function_mut(move |_, (): ()| {
lua_ecs
.lock()
.remove_resource::()
.context("recording not active")
.map_err(Error::external)?
.finish()
.map_err(Error::external)
})?,
)?;
globals.set(
"client",
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(
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:?}");
}
});
}
}
}