quectocraft/src/plugins/mod.rs

178 lines
6.0 KiB
Rust

use std::{path::Path, fs::{self, read_dir}};
use log::warn;
use mlua::{Lua, Table, chunk, LuaSerdeExt};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::network::Player;
use self::plugin::Plugin;
mod plugin;
pub struct Plugins<'lua> {
lua: &'lua Lua,
plugins: Vec<Plugin<'lua>>
}
impl <'lua> Plugins<'lua> {
pub fn new(lua: &'lua Lua) -> Result<Self, mlua::Error> {
lua.load(chunk!{
server = { players = {} }
_qc = { responses = {} }
function server.sendMessage(player, message)
if type(player) ~= "string" then
error("player must be a string")
end
if type(message) == "table" then
table.insert(_qc.responses, {type = "message", player = player, message = message})
elseif type(message) == "string" then
table.insert(_qc.responses, {type = "message", player = player, message = { text = message}})
else
error("message must be a string or table")
end
end
function server.broadcast(message)
if type(message) == "table" then
table.insert(_qc.responses, { type = "broadcast", message = message })
elseif type(message) == "string" then
table.insert(_qc.responses, { type = "broadcast", message = { text = message } })
else
error("message must be a string or table")
end
end
function server.initLogger(plugin)
local function log_for(level)
return function(message)
table.insert(_qc.responses, {
type = "log", origin = plugin.id, message = message, level = level
})
end
end
return {
trace = log_for(0),
debug = log_for(1),
info = log_for(2),
warn = log_for(3),
error = log_for(4),
}
end
}).exec()?;
Ok(Self {
lua,
plugins: Vec::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);
}
}
pub fn count(&self) -> usize {
self.plugins.len()
}
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 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);
}
}
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(tag="type")]
pub enum Response {
#[serde(rename = "log")]
Log { level: i32, origin: String, message: String },
#[serde(rename = "message")]
Message { player: String, message: serde_json::Value },
#[serde(rename = "broadcast")]
Broadcast { message: serde_json::Value },
}