use log::{debug, error, info, warn}; use regex::Regex; use rhai::{packages::Package, CustomType, Engine, EvalAltResult, FnPtr, Scope, TypeBuilder, AST}; use rhai_rand::RandomPackage; use std::{ collections::HashMap, ffi::OsStr, fmt, fs, path::Path, sync::{Arc, Mutex}, }; use crate::{ error::AppError, player::Player, utils::{send_chat_message, stream_write_short, write_chat_stream}, world::World, }; pub struct Extensions { extensions: Vec, players: PlayersWrapper, } impl Extensions { pub fn run_command( &self, key: String, player: u8, argv: Vec, ) -> Result { // Here I'm calling write_chat_stream multiple times. This is because the stock minecraft // chat has a length limit of 64 characters, which is pathetically small. There is a // classic extension to support an unlimited number of characters, but it's not guaranteed // that the client will support it, so the next best option is to just send multiple // messages, they're newline seperated anyway. I am aware that repeated stream writes are // not the best option however, and that at some point I should switch to buffered streams. // TODO: Use buffered streams (That's everywhere not just here) // Reserve extension listing command if &key == "extensions" { let mut res_data: Vec = Vec::new(); res_data.extend_from_slice(&write_chat_stream("Extension listing".to_string())); for extension in &self.extensions { res_data.extend_from_slice(&write_chat_stream(format!( "&a{} &bv{}", extension.metadata.name, extension.metadata.version ))); } self.players.0.lock().unwrap()[player as usize] .outgoing_data .extend_from_slice(&res_data); return Ok(true); } // Reserve command listing command if &key == "help" { let mut res_data: Vec = Vec::new(); res_data.extend_from_slice(&write_chat_stream("Command listing".to_string())); res_data.extend_from_slice(&write_chat_stream(format!( "&c{} &a[{}]", "help", "Builtin" ))); res_data.extend_from_slice(&write_chat_stream(format!( "&c{} &a[{}]", "extensions", "Builtin" ))); res_data.extend_from_slice(&write_chat_stream(format!( "&c{} &a[{}]", "kick", "Builtin" ))); res_data.extend_from_slice(&write_chat_stream(format!("&c{} &a[{}]", "tp", "Builtin"))); for extension in &self.extensions { for command in extension.commands.keys() { res_data.extend_from_slice(&write_chat_stream(format!( "&c{} &a[{}]", command, extension.metadata.name ))); } } self.players.0.lock().unwrap()[player as usize] .outgoing_data .extend_from_slice(&res_data); return Ok(true); } for extension in &self.extensions { if let Some(key_value) = extension.commands.get(&key) { // Vector transfer number 2 (get the shit joke?) let mut argv_rhai_array: rhai::Array = rhai::Array::new(); for arg in &argv { argv_rhai_array.push(arg.into()); } key_value.call::<()>( &extension.engine, &extension.ast, (player, argv_rhai_array), )?; return Ok(true); } } Ok(false) } pub fn run_event(&self, event_type: EventType, event: Event) -> Event { let mut is_cancelled = false; for extension in &self.extensions { if let Some(key_value) = extension.event_listeners.get(&event_type) { is_cancelled = match key_value.call::(&extension.engine, &extension.ast, (event,)) { Ok(result) => result.is_cancelled, Err(err) => { error!("{} raised: {}", extension.metadata.name, err); break; } }; } } let mut response = Event::new(); response.is_cancelled = is_cancelled; response } } pub struct Extension { ast: AST, engine: Engine, commands: HashMap, event_listeners: HashMap, metadata: ExtensionMetadata, } ////// BEGIN RHAI DEFINITIONS ////// fn info(msg: &str) { info!("{}", msg); } fn warn(msg: &str) { warn!("{}", msg); } fn error(msg: &str) { error!("{}", msg); } fn debug(msg: &str) { debug!("{}", msg); } #[derive(Debug, Clone, Eq, PartialEq, CustomType)] #[rhai_type(name = "Version", extra = Self::build_extra)] struct Version { major: u16, minor: u16, patch: u16, prerelease: String, build: String, } impl Version { pub fn display(&self) -> String { let mut base = format!("{}.{}.{}", self.major, self.minor, self.patch); if !self.prerelease.is_empty() { base.push_str(&format!("-{}", self.prerelease).to_string()); } if !self.build.is_empty() { base.push_str(&format!("+{}", self.build).to_string()); } base } fn parse(version_string: String) -> Result> { let Ok(re) = Regex::new( r"^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", ) else { return Err("Failed to create regex".into()); }; let Some(version_parts) = re.captures(&version_string) else { return Err("Invalid Extension Version".into()); }; let mut prerelease: String = "".to_string(); let mut build: String = "".to_string(); if version_parts.name("prerelease").is_some() { prerelease = version_parts["prerelease"].to_string(); } if version_parts.name("buildmetadata").is_some() { build = version_parts["buildmetadata"].to_string(); } Ok(Version { major: version_parts["major"].parse::().unwrap(), minor: version_parts["minor"].parse::().unwrap(), patch: version_parts["patch"].parse::().unwrap(), prerelease, build, }) } fn build_extra(builder: &mut TypeBuilder) { // Register constructor function builder.with_fn("Version", Self::parse); } } impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.display()) } } #[derive(Debug, Clone, Eq, PartialEq, CustomType)] #[rhai_type(name = "Metadata", extra = Self::build_extra)] struct ExtensionMetadata { name: String, author: String, version: Version, } impl ExtensionMetadata { fn new(name: String, author: String, version: Version) -> Self { Self { name, author, version, } } fn build_extra(builder: &mut TypeBuilder) { // Register constructor function builder.with_fn("Metadata", Self::new); } } #[derive(Debug, Clone, CustomType)] #[rhai_type(name = "Player")] struct RhaiPlayer { id: u8, } #[derive(Debug, Clone, CustomType)] #[rhai_type(name = "PlayersWrapper", extra=Self::build_extra)] pub struct PlayersWrapper(Arc>); impl PlayersWrapper { pub fn new(players: Arc>) -> Self { Self(players) } fn send_message(self, player: u8, message: String) -> Result<(), Box> { let mut players = self.0.lock().unwrap(); players[player as usize] .outgoing_data .extend_from_slice(&send_chat_message(255, "".to_string(), message)); Ok(()) } fn send_all(self, message: String) -> Result<(), Box> { let mut players = self.0.lock().unwrap(); let data = &send_chat_message(255, "".to_string(), message); for i in 0..255 { if players[i].id != 255 { players[i].outgoing_data.extend_from_slice(data); } } Ok(()) } fn username(self, player: u8) -> String { let players = self.0.lock().unwrap(); players[player as usize].username.clone() } fn build_extra(builder: &mut TypeBuilder) { builder.with_fn("send_message", Self::send_message); builder.with_fn("send_all", Self::send_all); builder.with_fn("username", Self::username); } } #[derive(Debug, Clone, CustomType)] #[rhai_type(name = "WorldWrapper", extra=Self::build_extra)] pub struct WorldWrapper(Arc>); impl WorldWrapper { pub fn new(world: Arc>) -> Self { Self(world) } pub fn set_block(self, players_wrapper: PlayersWrapper, position: Vec3, block_type: u8) { let mut world_dat = self.0.lock().unwrap(); let world_offset: u32 = position.x as u32 + (position.z as u32 * world_dat.size_x as u32) + (position.y as u32 * world_dat.size_x as u32 * world_dat.size_z as u32); world_dat.data[world_offset as usize] = block_type; let mut update_block_bytes: Vec = Vec::new(); update_block_bytes.push(0x06); update_block_bytes.extend_from_slice(&stream_write_short(position.x)); update_block_bytes.extend_from_slice(&stream_write_short(position.y)); update_block_bytes.extend_from_slice(&stream_write_short(position.z)); update_block_bytes.push(block_type); let mut players = players_wrapper.0.lock().unwrap(); for i in 0..players.len() { if players[i].id != 255 { players[i] .outgoing_data .extend_from_slice(&update_block_bytes); } } } // TODO: Finish this // pub fn get_block(&self, position: Vec3) -> u8 { // let mut world = self.0.lock().unwrap(); // } fn build_extra(builder: &mut TypeBuilder) { builder.with_fn("set_block", Self::set_block); // builder.with_fn("get_block", Self::get_block); } } #[derive(Debug, Clone, CustomType)] #[rhai_type(name = "Context", extra = Self::build_extra)] struct Context { #[rhai_type(skip)] commands: HashMap, #[rhai_type(skip)] event_listener: HashMap, } #[derive(Debug, Clone, Copy, PartialEq, CustomType)] #[rhai_type(name = "Vec3", extra = Self::build_extra)] pub struct Vec3 { pub x: i16, pub y: i16, pub z: i16, } // Custom type API impl Vec3 { fn new(x: i64, y: i64, z: i64) -> Self { Self { x: x.try_into().unwrap(), y: y.try_into().unwrap(), z: z.try_into().unwrap(), } } fn build_extra(builder: &mut TypeBuilder) { builder.with_fn("Vec3", Self::new); } } #[derive(Debug, Clone, Eq, Hash, PartialEq)] pub enum EventType { BlockBreak, PlayerLeave, } #[derive(Debug, Clone, Copy, CustomType)] #[rhai_type(name = "Event", extra = Self::build_extra)] pub struct Event { pub player: u8, pub position: Vec3, pub selected_block: u8, pub is_cancelled: bool, } impl Event { pub fn new() -> Event { Event { player: 255, position: (Vec3 { x: 0, y: 0, z: 0 }), selected_block: 0, is_cancelled: false, } } pub fn cancel(&mut self) { self.is_cancelled = true; } fn build_extra(builder: &mut TypeBuilder) { builder.with_fn("Event", Self::new); builder.with_fn("cancel", Self::cancel); } } impl Context { fn new() -> Self { Self { commands: HashMap::new(), event_listener: HashMap::new(), } } fn register_command(&mut self, name: String, callback: FnPtr) { self.commands.insert(name, callback); } fn add_event_listener(&mut self, event: &str, callback: FnPtr) { let event_listener: EventType = match event { "block_break" => EventType::BlockBreak, "player_leave" => EventType::PlayerLeave, _ => { warn!("An event listener was created with invalid type: {}", event); return; } }; self.event_listener.insert(event_listener, callback); } fn build_extra(builder: &mut TypeBuilder) { builder.with_fn("Context", Self::new); builder.with_fn("register_command", Self::register_command); builder.with_fn("add_event_listener", Self::add_event_listener); } } ////// END RHAI DEFINITIONS ////// impl Extensions { pub fn init(players: PlayersWrapper, world: WorldWrapper) -> Result { if !Path::new("./extensions/").exists() { let _ = fs::create_dir("./extensions/"); } let extensions_listing = fs::read_dir("./extensions")?; let mut extensions = Extensions { extensions: Vec::new(), players: players.clone(), }; for extension in extensions_listing { let extension_path = extension?.path(); if extension_path.extension() != Some(OsStr::new("rhai")) { break; } info!("Loading extension {}", extension_path.display()); let mut engine = Engine::new(); let random = RandomPackage::new(); random.register_into_engine(&mut engine); engine.set_max_expr_depths(50, 50); engine.build_type::(); engine.build_type::(); engine.build_type::(); engine.build_type::(); engine.build_type::(); engine.build_type::(); engine.build_type::(); engine.build_type::(); engine.register_fn("info", info); engine.register_fn("warn", warn); engine.register_fn("error", error); engine.register_fn("debug", debug); let ast = match engine.compile_file(extension_path.clone()) { Ok(result) => result, Err(error) => { error!( "Rhai plugin compilation failed for {}, reason: {}", extension_path.display(), error ); continue; } }; let mut scope = Scope::new(); let extension_metadata = match engine.call_fn::(&mut scope, &ast, "metadata", ()) { Ok(result) => result, Err(error) => { error!( "Rhai plugin with path {} missing critical section metadata! {}", extension_path.display(), error ); continue; } }; let mut current_extension = Extension { ast, engine, commands: HashMap::new(), event_listeners: HashMap::new(), metadata: extension_metadata, }; let ctx = match current_extension.engine.call_fn::( &mut scope, ¤t_extension.ast, "init", ( PlayersWrapper::new(players.0.clone()), WorldWrapper::new(world.0.clone()), ), ) { Ok(result) => result, Err(error) => { error!( "Plugin {} failed to init: {}", current_extension.metadata.name, error ); continue; } }; for (key, value) in ctx.commands.iter() { current_extension .commands .insert(key.to_string(), value.clone()); } for (key, value) in ctx.event_listener.iter() { current_extension .event_listeners .insert(key.clone(), value.clone()); } info!( "Loaded {} v{}", current_extension.metadata.name, current_extension.metadata.version, ); extensions.extensions.push(current_extension); } for extension in &extensions.extensions { for command_name in extension.commands.keys() { info!( "Extension {} v{} has reserved command: {}", extension.metadata.name, extension.metadata.version, command_name ); } } Ok(extensions) } }