diff --git a/pumpkin/src/command/commands/cmd_plugin.rs b/pumpkin/src/command/commands/cmd_plugin.rs new file mode 100644 index 00000000..039bed8c --- /dev/null +++ b/pumpkin/src/command/commands/cmd_plugin.rs @@ -0,0 +1,198 @@ +use async_trait::async_trait; +use pumpkin_core::text::{color::NamedColor, hover::HoverEvent, TextComponent}; + +use crate::{ + command::{ + args::{arg_simple::SimpleArgConsumer, Arg, ConsumedArgs}, + tree::CommandTree, + tree_builder::{argument, literal, require}, + CommandError, CommandExecutor, CommandSender, + }, + entity::player::PermissionLvl, + PLUGIN_MANAGER, +}; + +use crate::command::CommandError::InvalidConsumption; + +const NAMES: [&str; 1] = ["plugin"]; + +const DESCRIPTION: &str = "Manage plugins."; + +const PLUGIN_NAME: &str = "plugin_name"; + +struct ListExecutor; + +#[async_trait] +impl CommandExecutor for ListExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + _args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + let plugin_manager = PLUGIN_MANAGER.lock().await; + let plugins = plugin_manager.list_plugins(); + + let message_text = if plugins.is_empty() { + "There are no loaded plugins." + } else if plugins.len() == 1 { + "There is 1 plugin loaded:\n" + } else { + &format!("There are {} plugins loaded:\n", plugins.len(),) + }; + let mut message = TextComponent::text(message_text); + + for (i, (metadata, loaded)) in plugins.clone().into_iter().enumerate() { + let fmt = if i == plugins.len() - 1 { + metadata.name.to_string() + } else { + format!("{}, ", metadata.name) + }; + let hover_text = format!( + "Version: {}\nAuthors: {}\nDescription: {}", + metadata.version, metadata.authors, metadata.description + ); + let component = if *loaded { + TextComponent::text_string(fmt) + .color_named(NamedColor::Green) + .hover_event(HoverEvent::ShowText(hover_text.into())) + } else { + TextComponent::text_string(fmt) + .color_named(NamedColor::Red) + .hover_event(HoverEvent::ShowText(hover_text.into())) + }; + message = message.add_child(component); + } + + sender.send_message(message).await; + + Ok(()) + } +} + +struct LoadExecutor; + +#[async_trait] +impl CommandExecutor for LoadExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + let Some(Arg::Simple(plugin_name)) = args.get(PLUGIN_NAME) else { + return Err(InvalidConsumption(Some(PLUGIN_NAME.into()))); + }; + let mut plugin_manager = PLUGIN_MANAGER.lock().await; + + if plugin_manager.is_plugin_loaded(plugin_name) { + sender + .send_message( + TextComponent::text_string(format!("Plugin {} is already loaded", plugin_name)) + .color_named(NamedColor::Red), + ) + .await; + return Ok(()); + } + + let result = plugin_manager.load_plugin(plugin_name).await; + + match result { + Ok(_) => { + sender + .send_message( + TextComponent::text_string(format!( + "Plugin {} loaded successfully", + plugin_name + )) + .color_named(NamedColor::Green), + ) + .await; + } + Err(e) => { + sender + .send_message( + TextComponent::text_string(format!( + "Failed to load plugin {}: {}", + plugin_name, e + )) + .color_named(NamedColor::Red), + ) + .await; + } + } + + Ok(()) + } +} + +struct UnloadExecutor; + +#[async_trait] +impl CommandExecutor for UnloadExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + let Some(Arg::Simple(plugin_name)) = args.get(PLUGIN_NAME) else { + return Err(InvalidConsumption(Some(PLUGIN_NAME.into()))); + }; + let mut plugin_manager = PLUGIN_MANAGER.lock().await; + + if !plugin_manager.is_plugin_loaded(plugin_name) { + sender + .send_message( + TextComponent::text_string(format!("Plugin {} is not loaded", plugin_name)) + .color_named(NamedColor::Red), + ) + .await; + return Ok(()); + } + + let result = plugin_manager.unload_plugin(plugin_name).await; + + match result { + Ok(_) => { + sender + .send_message( + TextComponent::text_string(format!( + "Plugin {} unloaded successfully", + plugin_name + )) + .color_named(NamedColor::Green), + ) + .await; + } + Err(e) => { + sender + .send_message( + TextComponent::text_string(format!( + "Failed to unload plugin {}: {}", + plugin_name, e + )) + .color_named(NamedColor::Red), + ) + .await; + } + } + + Ok(()) + } +} + +pub fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(NAMES, DESCRIPTION).with_child( + require(&|sender| sender.has_permission_lvl(PermissionLvl::Three)) + .with_child( + literal("load") + .with_child(argument(PLUGIN_NAME, &SimpleArgConsumer).execute(&LoadExecutor)), + ) + .with_child( + literal("unload") + .with_child(argument(PLUGIN_NAME, &SimpleArgConsumer).execute(&UnloadExecutor)), + ) + .with_child(literal("list").execute(&ListExecutor)), + ) +} diff --git a/pumpkin/src/command/commands/mod.rs b/pumpkin/src/command/commands/mod.rs index 78e9d32f..182845ac 100644 --- a/pumpkin/src/command/commands/mod.rs +++ b/pumpkin/src/command/commands/mod.rs @@ -7,6 +7,7 @@ pub mod cmd_help; pub mod cmd_kick; pub mod cmd_kill; pub mod cmd_list; +pub mod cmd_plugin; pub mod cmd_plugins; pub mod cmd_pumpkin; pub mod cmd_say; diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index 74f7d670..9d6547e5 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -11,7 +11,7 @@ use args::ConsumedArgs; use async_trait::async_trait; use commands::{ cmd_clear, cmd_fill, cmd_gamemode, cmd_give, cmd_help, cmd_kick, cmd_kill, cmd_list, - cmd_plugins, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, cmd_time, + cmd_plugin, cmd_plugins, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, cmd_time, cmd_worldborder, }; use dispatcher::CommandError; @@ -119,6 +119,7 @@ pub fn default_dispatcher<'a>() -> Arc> { dispatcher.register(cmd_help::init_command_tree()); dispatcher.register(cmd_kill::init_command_tree()); dispatcher.register(cmd_kick::init_command_tree()); + dispatcher.register(cmd_plugin::init_command_tree()); dispatcher.register(cmd_plugins::init_command_tree()); dispatcher.register(cmd_worldborder::init_command_tree()); dispatcher.register(cmd_teleport::init_command_tree());