feat: add event listeners

This commit is contained in:
Ryan 2025-02-20 21:07:41 -05:00
parent c2da997b71
commit bd6698c4b4
Signed by: ErrorNoInternet
GPG Key ID: 2486BFB7B1E6A4A3
5 changed files with 126 additions and 25 deletions

View File

@ -1,7 +1,7 @@
local center = { x = 0, z = 0 } local center = { x = 0, z = 0 }
local radius = 100 local radius = 100
function on_tick() function log_player_positions()
local entities = client:find_entities(function(e) local entities = client:find_entities(function(e)
return e.kind == "minecraft:player" return e.kind == "minecraft:player"
and e.position.x > center.x - radius + 1 and e.position.x > center.x - radius + 1
@ -17,8 +17,14 @@ end
function on_init() function on_init()
info("client initialized, setting information...") info("client initialized, setting information...")
client:set_client_information({ view_distance = 16 }) client:set_client_information({ view_distance = 16 })
end
function on_login() add_listener("login", function()
info("player successfully logged in!") info("player successfully logged in!")
end)
add_listener("death", function()
warn("player died!")
end, "warn_player_died")
add_listener("tick", log_player_positions)
end end

View File

@ -1,9 +1,14 @@
use crate::{State, commands::CommandSource, http::serve, lua}; use crate::{
State,
commands::CommandSource,
http::serve,
lua::{self, events::register_functions},
};
use azalea::prelude::*; use azalea::prelude::*;
use hyper::{server::conn::http1, service::service_fn}; use hyper::{server::conn::http1, service::service_fn};
use hyper_util::rt::TokioIo; use hyper_util::rt::TokioIo;
use log::{debug, error, info, trace}; use log::{debug, error, info, trace};
use mlua::{Function, IntoLuaMulti, Table}; use mlua::{Function, IntoLuaMulti};
use tokio::net::TcpListener; use tokio::net::TcpListener;
pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow::Result<()> { pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow::Result<()> {
@ -32,23 +37,22 @@ pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow:
CommandSource { CommandSource {
client, client,
message, message,
state, state: state.clone(),
} }
.reply(&format!("{error:?}")); .reply(&format!("{error:?}"));
} }
} }
call_lua_handler(&globals, "on_chat", ()); call_listeners(&state, "chat", formatted_message.to_string()).await;
} }
Event::Death(Some(packet)) => { Event::Death(Some(packet)) => {
let death_data = state.lua.create_table()?; let death_data = state.lua.create_table()?;
death_data.set("message", packet.message.to_string())?; death_data.set("message", packet.message.to_string())?;
death_data.set("player_id", packet.player_id)?; death_data.set("player_id", packet.player_id)?;
call_listeners(&state, "death", death_data).await;
call_lua_handler(&globals, "on_death", death_data);
} }
Event::Tick => call_lua_handler(&globals, "on_tick", ()), Event::Login => call_listeners(&state, "login", ()).await,
Event::Login => call_lua_handler(&globals, "on_login", ()), Event::Tick => call_listeners(&state, "tick", ()).await,
Event::Init => { Event::Init => {
debug!("client initialized"); debug!("client initialized");
@ -58,7 +62,12 @@ pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow:
inner: Some(client), inner: Some(client),
}, },
)?; )?;
call_lua_handler(&globals, "on_init", ()); register_functions(&state.lua, &globals, state.clone()).await?;
if let Ok(on_init) = globals.get::<Function>("on_init")
&& let Err(error) = on_init.call::<()>(())
{
error!("failed to call lua on_init function: {error:?}");
}
if let Some(address) = state.http_address { if let Some(address) = state.http_address {
let listener = TcpListener::bind(address).await.map_err(|error| { let listener = TcpListener::bind(address).await.map_err(|error| {
@ -77,7 +86,7 @@ pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow:
async move { serve(request, request_state).await } async move { serve(request, request_state).await }
}); });
tokio::task::spawn(async move { tokio::spawn(async move {
if let Err(error) = http1::Builder::new() if let Err(error) = http1::Builder::new()
.serve_connection(TokioIo::new(stream), service) .serve_connection(TokioIo::new(stream), service)
.await .await
@ -94,10 +103,12 @@ pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow:
Ok(()) Ok(())
} }
fn call_lua_handler<T: IntoLuaMulti>(globals: &Table, name: &str, data: T) { async fn call_listeners<T: Clone + IntoLuaMulti>(state: &State, event_type: &str, data: T) {
if let Ok(handler) = globals.get::<Function>(name) if let Some(listeners) = state.event_listeners.lock().await.get(event_type) {
&& let Err(error) = handler.call::<()>(data) for (_, listener) in listeners {
{ if let Err(error) = listener.call_async::<()>(data.clone()).await {
error!("failed to call lua {name} function: {error:?}"); error!("failed to call lua event listener for {event_type}: {error:?}");
}
}
} }
} }

79
src/lua/events.rs Normal file
View File

@ -0,0 +1,79 @@
use crate::State;
use futures::executor::block_on;
use mlua::{Function, Lua, Result, Table};
use std::time::{SystemTime, UNIX_EPOCH};
pub async fn register_functions(lua: &Lua, globals: &Table, state: State) -> Result<()> {
let l = state.event_listeners.clone();
globals.set(
"add_listener",
lua.create_function(
move |_, (event_type, callback, id): (String, Function, Option<String>)| {
let mut l = block_on(l.lock());
l.entry(event_type).or_default().push((
id.unwrap_or(callback.info().name.unwrap_or(format!(
"anonymous @ {}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
))),
callback,
));
Ok(())
},
)?,
)?;
let l = state.event_listeners.clone();
globals.set(
"remove_listener",
lua.create_function(move |_, (event_type, target_id): (String, String)| {
let mut l = block_on(l.lock());
let empty = if let Some(listeners) = l.get_mut(&event_type) {
listeners.retain(|(id, _)| target_id != *id);
listeners.is_empty()
} else {
false
};
if empty {
l.remove(&event_type);
}
Ok(())
})?,
)?;
globals.set(
"get_listeners",
lua.create_function(move |lua, (): ()| {
let l = block_on(state.event_listeners.lock());
let listeners = lua.create_table()?;
for (event_type, callbacks) in l.iter() {
let type_listeners = lua.create_table()?;
for (id, callback) in callbacks {
let listener = lua.create_table()?;
let i = callback.info();
if let Some(n) = i.name {
listener.set("name", n)?;
}
if let Some(l) = i.line_defined {
listener.set("line_defined", l)?;
}
if let Some(s) = i.source {
listener.set("source", s)?;
}
type_listeners.set(id.to_owned(), listener)?;
}
listeners.set(event_type.to_owned(), type_listeners)?;
}
Ok(listeners)
})?,
)?;
Ok(())
}

View File

@ -2,6 +2,7 @@ pub mod block;
pub mod client; pub mod client;
pub mod container; pub mod container;
pub mod direction; pub mod direction;
pub mod events;
pub mod logging; pub mod logging;
pub mod vec3; pub mod vec3;
@ -26,8 +27,8 @@ pub fn register_functions(lua: &Lua, globals: &Table) -> mlua::Result<()> {
})?, })?,
)?; )?;
logging::register_functions(lua, globals)?; block::register_functions(lua, globals)?;
block::register_functions(lua, globals) logging::register_functions(lua, globals)
} }
pub fn reload(lua: &Lua) -> Result<(), Error> { pub fn reload(lua: &Lua) -> Result<(), Error> {

View File

@ -11,16 +11,19 @@ use clap::Parser;
use commands::{CommandSource, register}; use commands::{CommandSource, register};
use events::handle_event; use events::handle_event;
use futures::lock::Mutex; use futures::lock::Mutex;
use mlua::Lua; use mlua::{Function, Lua};
use std::{net::SocketAddr, path::PathBuf, sync::Arc}; use std::{collections::HashMap, net::SocketAddr, path::PathBuf, sync::Arc};
const DEFAULT_SCRIPT_PATH: &str = "errornowatcher.lua"; const DEFAULT_SCRIPT_PATH: &str = "errornowatcher.lua";
type ListenerMap = HashMap<String, Vec<(String, Function)>>;
#[derive(Default, Clone, Component)] #[derive(Default, Clone, Component)]
pub struct State { pub struct State {
lua: Lua, lua: Lua,
http_address: Option<SocketAddr>, event_listeners: Arc<Mutex<ListenerMap>>,
commands: Arc<CommandDispatcher<Mutex<CommandSource>>>, commands: Arc<CommandDispatcher<Mutex<CommandSource>>>,
http_address: Option<SocketAddr>,
} }
#[tokio::main] #[tokio::main]
@ -53,8 +56,9 @@ async fn main() -> anyhow::Result<()> {
.set_handler(handle_event) .set_handler(handle_event)
.set_state(State { .set_state(State {
lua, lua,
http_address: args.http_address, event_listeners: Arc::new(Mutex::new(HashMap::new())),
commands: Arc::new(commands), commands: Arc::new(commands),
http_address: args.http_address,
}) })
.start( .start(
if username.contains('@') { if username.contains('@') {