quectocraft/src/plugins/mod.rs

184 lines
6.4 KiB
Rust

use std::{fs::read_dir, rc::Rc, cell::RefCell, collections::HashMap};
use log::{warn, info};
use mlua::{Lua, Table, LuaSerdeExt};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{network::Player, protocol::command::Commands};
use self::plugin::Plugin;
mod init_lua;
mod plugin;
#[derive(Serialize, Deserialize)]
#[serde(tag="type")]
pub enum Response {
#[serde(rename = "message")]
Message { player: String, message: serde_json::Value },
#[serde(rename = "broadcast")]
Broadcast { message: serde_json::Value },
#[serde(rename = "disconnect")]
Disconnect { player: String, reason: serde_json::Value },
}
pub struct Plugins<'lua> {
lua: &'lua Lua,
plugins: Vec<Plugin<'lua>>,
cmd_owners: HashMap<String, usize>,
}
impl <'lua> Plugins<'lua> {
pub fn new(lua: &'lua Lua) -> Result<Self, mlua::Error> {
init_lua::init(lua)?;
Ok(Self {
lua,
plugins: Vec::new(),
cmd_owners: HashMap::new(),
})
}
pub fn load_plugins(&mut self) {
let files = read_dir("plugins").expect("couldn't read plugins directory");
for file in files {
let file = file.expect("couldn't read contents of plugins directory");
let path = if file.file_type().expect("couldn't get type of plugin file").is_dir() {
let mut main = file.path();
main.push("main.lua");
main
} else {
file.path()
};
let pl = Plugin::load(&path, self.lua).expect("error loading plugin");
self.plugins.push(pl);
info!("Loaded plugin '{}'", file.file_name().to_string_lossy());
}
}
pub fn get_responses(&self) -> Vec<Response> {
match self.get_responses_inner() {
Ok(x) => x,
Err(e) => {
warn!("Error getting responses: {}", e);
Vec::new()
}
}
}
fn get_responses_inner(&self) -> Result<Vec<Response>, Box<dyn std::error::Error>> {
let qc: Table = self.lua.globals().get("_qc")?;
let responses: Vec<Response> = self.lua.from_value(qc.get("responses")?)?;
qc.set("responses", self.lua.create_table()?)?;
Ok(responses)
}
pub fn init(&self) {
for pl in &self.plugins {
if let Some(init) = &pl.event_handlers.init {
if let Err(e) = init.call::<_, ()>(()) {
warn!("Error in plugin {}: {}", pl.name, e);
}
}
}
}
pub fn register_commands(&mut self, commands: Commands) -> Result<Commands, mlua::Error> {
let commands = Rc::new(RefCell::new(commands));
let cmd_owners = Rc::new(RefCell::new(HashMap::new()));
for (i, pl) in self.plugins.iter().enumerate() {
let commands_2 = commands.clone();
let cmd_owners_2 = cmd_owners.clone();
let pl_id = pl.id.clone();
let add_command = self.lua.create_function(move |_, name: String| {
let scoped_name = format!("{}:{}", pl_id, name);
let mut cmds = commands_2.borrow_mut();
let id1 = cmds.create_simple_cmd(&name);
let id2 = cmds.create_simple_cmd(&scoped_name);
if id1.is_none() || id2.is_none() {
return Ok(mlua::Nil)
}
cmd_owners_2.borrow_mut().insert(name, i);
cmd_owners_2.borrow_mut().insert(scoped_name, i);
Ok(mlua::Nil)
})?;
let registry = self.lua.create_table()?;
registry.set("addCommand", add_command)?;
if let Some(init) = &pl.event_handlers.register_commands {
if let Err(e) = init.call::<_, ()>((registry.clone(),)) {
warn!("Error in plugin {}: {}", pl.name, e);
}
}
}
let cb = commands.borrow();
self.cmd_owners = (*cmd_owners.borrow()).clone();
Ok((*cb).clone())
}
pub fn player_join(&self, player: &Player) {
if let Err(e) = self.add_player(player) {
warn!("Error adding player: {}", e);
return
}
for pl in &self.plugins {
if let Some(init) = &pl.event_handlers.player_join {
if let Err(e) = init.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) {
warn!("Error in plugin {}: {}", pl.name, e);
}
}
}
}
fn add_player(&self, player: &Player) -> Result<(), mlua::Error> {
let server: Table = self.lua.globals().get("server")?;
let players: Table = server.get("players")?;
players.set(player.uuid.to_string(), player.name.as_str())?;
Ok(())
}
pub fn player_leave(&self, player: &Player) {
if let Err(e) = self.remove_player(player.uuid) {
warn!("Error removing player: {}", e);
return
}
for pl in &self.plugins {
if let Some(func) = &pl.event_handlers.player_leave {
if let Err(e) = func.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) {
warn!("Error in plugin {}: {}", pl.name, e);
}
}
}
}
fn remove_player(&self, uuid: Uuid) -> Result<(), mlua::Error> {
let server: Table = self.lua.globals().get("server")?;
let players: Table = server.get("players")?;
players.set(uuid.to_string(), mlua::Nil)?;
Ok(())
}
pub fn chat_message(&self, player: &Player, message: &str) {
for pl in &self.plugins {
if let Some(func) = &pl.event_handlers.chat_message {
if let Err(e) = func.call::<_, ()>((message, player.name.as_str(), player.uuid.to_string())) {
warn!("Error in plugin {}: {}", pl.name, e);
}
}
}
}
pub fn command(&self, player: &Player, command: &str, args: &str) {
if let Some(owner) = self.cmd_owners.get(command) {
let pl = &self.plugins[*owner];
if let Some(func) = &pl.event_handlers.command {
if let Err(e) = func.call::<_, ()>((command, args, player.name.as_str(), player.uuid.to_string())) {
warn!("Error in plugin {}: {}", pl.name, e);
}
} else {
warn!("Plugin {} registered a command but no command handler was found", pl.id);
}
}
}
}