Command system rewrite

Note: Help commands is currently disabled
This commit is contained in:
Snowiiii 2024-09-07 20:26:19 +02:00
parent 2ee9710f93
commit 3d14e1310b
8 changed files with 98 additions and 93 deletions

View File

@ -1,7 +1,7 @@
use std::f32::consts::PI; use std::f32::consts::PI;
use crate::{ use crate::{
commands::{handle_command, CommandSender}, commands::CommandSender,
entity::player::{ChatMode, Hand, Player}, entity::player::{ChatMode, Hand, Player},
server::Server, server::Server,
world::player_chunker, world::player_chunker,
@ -214,7 +214,8 @@ impl Player {
} }
pub fn handle_chat_command(&mut self, server: &mut Server, command: SChatCommand) { pub fn handle_chat_command(&mut self, server: &mut Server, command: SChatCommand) {
handle_command(&mut CommandSender::Player(self), server, &command.command); let dispatcher = server.command_dispatcher.clone();
dispatcher.handle_command(&mut CommandSender::Player(self), server, &command.command);
} }
pub fn handle_player_ground(&mut self, _server: &mut Server, ground: SSetPlayerGround) { pub fn handle_player_ground(&mut self, _server: &mut Server, ground: SSetPlayerGround) {

View File

@ -1,74 +1,74 @@
use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError; // use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError;
use crate::commands::dispatcher::{CommandDispatcher, InvalidTreeError}; // use crate::commands::dispatcher::{CommandDispatcher, InvalidTreeError};
use crate::commands::tree::{Command, CommandTree, ConsumedArgs, RawArgs}; // use crate::commands::tree::{Command, CommandTree, ConsumedArgs, RawArgs};
use crate::commands::tree_builder::argument; // use crate::commands::tree_builder::argument;
use crate::commands::{dispatcher_init, CommandSender, DISPATCHER}; // use crate::commands::{CommandSender};
use pumpkin_core::text::TextComponent; // use pumpkin_core::text::TextComponent;
const NAMES: [&str; 3] = ["help", "h", "?"]; // const NAMES: [&str; 3] = ["help", "h", "?"];
const DESCRIPTION: &str = "Print a help message."; // const DESCRIPTION: &str = "Print a help message.";
const ARG_COMMAND: &str = "command"; // const ARG_COMMAND: &str = "command";
fn consume_arg_command(_src: &CommandSender, args: &mut RawArgs) -> Option<String> { // fn consume_arg_command(_src: &CommandSender, args: &mut RawArgs) -> Option<String> {
let s = args.pop()?; // let s = args.pop()?;
let dispatcher = DISPATCHER.get_or_init(dispatcher_init); // let dispatcher = DISPATCHER.get_or_init(dispatcher_init);
dispatcher.get_tree(s).ok().map(|tree| tree.names[0].into()) // dispatcher.get_tree(s).ok().map(|tree| tree.names[0].into())
} // }
fn parse_arg_command<'a>( // fn parse_arg_command<'a>(
consumed_args: &'a ConsumedArgs, // consumed_args: &'a ConsumedArgs,
dispatcher: &'a CommandDispatcher, // dispatcher: &'a CommandDispatcher,
) -> Result<&'a CommandTree<'a>, InvalidTreeError> { // ) -> Result<&'a CommandTree<'a>, InvalidTreeError> {
let command_name = consumed_args // let command_name = consumed_args
.get(ARG_COMMAND) // .get(ARG_COMMAND)
.ok_or(InvalidConsumptionError(None))?; // .ok_or(InvalidConsumptionError(None))?;
dispatcher // dispatcher
.get_tree(command_name) // .get_tree(command_name)
.map_err(|_| InvalidConsumptionError(Some(command_name.into()))) // .map_err(|_| InvalidConsumptionError(Some(command_name.into())))
} // }
pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { // pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION) // CommandTree::new(NAMES, DESCRIPTION)
.with_child( // .with_child(
argument(ARG_COMMAND, consume_arg_command).execute(&|sender, _, args| { // argument(ARG_COMMAND, consume_arg_command).execute(&|sender, _, args| {
let dispatcher = DISPATCHER.get_or_init(dispatcher_init); // let dispatcher = DISPATCHER.get_or_init(dispatcher_init);
let tree = parse_arg_command(args, dispatcher)?; // let tree = parse_arg_command(args, dispatcher)?;
sender.send_message(TextComponent::text(&format!( // sender.send_message(TextComponent::text(&format!(
"{} - {} Usage: {}", // "{} - {} Usage: {}",
tree.names.join("/"), // tree.names.join("/"),
tree.description, // tree.description,
tree // tree
))); // )));
Ok(()) // Ok(())
}), // }),
) // )
.execute(&|sender, _, _args| { // .execute(&|sender, _, _args| {
let dispatcher = DISPATCHER.get_or_init(dispatcher_init); // let dispatcher = DISPATCHER.get_or_init(dispatcher_init);
let mut keys: Vec<&str> = dispatcher.commands.keys().copied().collect(); // let mut keys: Vec<&str> = dispatcher.commands.keys().copied().collect();
keys.sort(); // keys.sort();
for key in keys { // for key in keys {
let Command::Tree(tree) = &dispatcher.commands[key] else { // let Command::Tree(tree) = &dispatcher.commands[key] else {
continue; // continue;
}; // };
sender.send_message(TextComponent::text(&format!( // sender.send_message(TextComponent::text(&format!(
"{} - {} Usage: {}", // "{} - {} Usage: {}",
tree.names.join("/"), // tree.names.join("/"),
tree.description, // tree.description,
tree // tree
))); // )));
} // }
Ok(()) // Ok(())
}) // })
} // }

View File

@ -1,3 +1,5 @@
use pumpkin_core::text::TextComponent;
use crate::commands::dispatcher::InvalidTreeError::{ use crate::commands::dispatcher::InvalidTreeError::{
InvalidConsumptionError, InvalidRequirementError, InvalidConsumptionError, InvalidRequirementError,
}; };
@ -17,12 +19,21 @@ pub(crate) enum InvalidTreeError {
InvalidRequirementError, InvalidRequirementError,
} }
pub(crate) struct CommandDispatcher<'a> { #[derive(Default)]
pub struct CommandDispatcher<'a> {
pub(crate) commands: HashMap<&'a str, Command<'a>>, pub(crate) commands: HashMap<&'a str, Command<'a>>,
} }
/// Stores registered [CommandTree]s and dispatches commands to them. /// Stores registered [CommandTree]s and dispatches commands to them.
impl<'a> CommandDispatcher<'a> { impl<'a> CommandDispatcher<'a> {
pub fn handle_command(&self, sender: &mut CommandSender, server: &mut Server, cmd: &str) {
if let Err(err) = self.dispatch(sender, server, cmd) {
sender.send_message(
TextComponent::text(&err).color_named(pumpkin_core::text::color::NamedColor::Red),
)
}
}
/// Execute a command using its corresponding [CommandTree]. /// Execute a command using its corresponding [CommandTree].
pub(crate) fn dispatch( pub(crate) fn dispatch(
&'a self, &'a self,

View File

@ -1,16 +1,13 @@
use pumpkin_core::text::TextComponent; use pumpkin_core::text::TextComponent;
use std::collections::HashMap;
use std::sync::OnceLock;
use crate::commands::dispatcher::CommandDispatcher; use crate::commands::dispatcher::CommandDispatcher;
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::server::Server;
mod arg_player; mod arg_player;
mod cmd_gamemode; mod cmd_gamemode;
mod cmd_help; mod cmd_help;
mod cmd_pumpkin; mod cmd_pumpkin;
mod cmd_stop; mod cmd_stop;
mod dispatcher; pub mod dispatcher;
mod tree; mod tree;
mod tree_builder; mod tree_builder;
mod tree_format; mod tree_format;
@ -64,32 +61,13 @@ impl<'a> CommandSender<'a> {
} }
} }
/// todo: reconsider using constant pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> {
/// let mut dispatcher = CommandDispatcher::default();
/// Central point from which commands are dispatched. Should always be initialized using
/// [dispatcher_init].
static DISPATCHER: OnceLock<CommandDispatcher> = OnceLock::new();
/// create [CommandDispatcher] instance for [DISPATCHER]
fn dispatcher_init<'a>() -> CommandDispatcher<'a> {
let mut dispatcher = CommandDispatcher {
commands: HashMap::new(),
};
dispatcher.register(cmd_pumpkin::init_command_tree()); dispatcher.register(cmd_pumpkin::init_command_tree());
dispatcher.register(cmd_gamemode::init_command_tree()); dispatcher.register(cmd_gamemode::init_command_tree());
dispatcher.register(cmd_stop::init_command_tree()); dispatcher.register(cmd_stop::init_command_tree());
dispatcher.register(cmd_help::init_command_tree()); // dispatcher.register(cmd_help::init_command_tree());
dispatcher dispatcher
} }
pub fn handle_command(sender: &mut CommandSender, server: &mut Server, cmd: &str) {
let dispatcher = DISPATCHER.get_or_init(dispatcher_init);
if let Err(err) = dispatcher.dispatch(sender, server, cmd) {
sender.send_message(
TextComponent::text(&err).color_named(pumpkin_core::text::color::NamedColor::Red),
)
}
}

View File

@ -44,6 +44,7 @@ pub(crate) struct CommandTree<'a> {
pub(crate) nodes: Vec<Node<'a>>, pub(crate) nodes: Vec<Node<'a>>,
pub(crate) children: Vec<usize>, pub(crate) children: Vec<usize>,
pub(crate) names: Vec<&'a str>, pub(crate) names: Vec<&'a str>,
#[expect(dead_code)]
pub(crate) description: &'a str, pub(crate) description: &'a str,
} }

View File

@ -10,7 +10,6 @@ use std::collections::HashMap;
use std::io::{self}; use std::io::{self};
use client::{interrupted, Client}; use client::{interrupted, Client};
use commands::handle_command;
use server::Server; use server::Server;
// Setup some tokens to allow us to identify which event is for which socket. // Setup some tokens to allow us to identify which event is for which socket.
@ -98,7 +97,12 @@ fn main() -> io::Result<()> {
if !out.is_empty() { if !out.is_empty() {
let mut server = server1.lock().await; let mut server = server1.lock().await;
handle_command(&mut commands::CommandSender::Console, &mut server, &out); let dispatcher = server.command_dispatcher.clone();
dispatcher.handle_command(
&mut commands::CommandSender::Console,
&mut server,
&out,
);
} }
} }
}); });

View File

@ -12,7 +12,7 @@ use packet::{Packet, PacketError, PacketType};
use pumpkin_config::RCONConfig; use pumpkin_config::RCONConfig;
use thiserror::Error; use thiserror::Error;
use crate::{commands::handle_command, server::Server}; use crate::server::Server;
mod packet; mod packet;
@ -194,7 +194,8 @@ impl RCONClient {
if self.logged_in { if self.logged_in {
let mut output = Vec::new(); let mut output = Vec::new();
let mut server = server.lock().await; let mut server = server.lock().await;
handle_command( let dispatcher = server.command_dispatcher.clone();
dispatcher.handle_command(
&mut crate::commands::CommandSender::Rcon(&mut output), &mut crate::commands::CommandSender::Rcon(&mut output),
&mut server, &mut server,
packet.get_body(), packet.get_body(),

View File

@ -24,7 +24,12 @@ use pumpkin_world::dimension::Dimension;
use pumpkin_registry::Registry; use pumpkin_registry::Registry;
use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey}; use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey};
use crate::{client::Client, entity::player::Player, world::World}; use crate::{
client::Client,
commands::{default_dispatcher, dispatcher::CommandDispatcher},
entity::player::Player,
world::World,
};
pub const CURRENT_MC_VERSION: &str = "1.21.1"; pub const CURRENT_MC_VERSION: &str = "1.21.1";
@ -35,6 +40,8 @@ pub struct Server {
pub plugin_loader: PluginLoader, pub plugin_loader: PluginLoader,
pub command_dispatcher: Arc<CommandDispatcher<'static>>,
pub worlds: Vec<Arc<tokio::sync::Mutex<World>>>, pub worlds: Vec<Arc<tokio::sync::Mutex<World>>>,
pub status_response: StatusResponse, pub status_response: StatusResponse,
// We cache the json response here so we don't parse it every time someone makes a Status request. // We cache the json response here so we don't parse it every time someone makes a Status request.
@ -81,6 +88,8 @@ impl Server {
None None
}; };
// First register default command, after that plugins can put in their own
let command_dispatcher = default_dispatcher();
log::info!("Loading Plugins"); log::info!("Loading Plugins");
let plugin_loader = PluginLoader::load(); let plugin_loader = PluginLoader::load();
@ -88,7 +97,6 @@ impl Server {
// TODO: load form config // TODO: load form config
"./world".parse().unwrap(), "./world".parse().unwrap(),
)); ));
Self { Self {
plugin_loader, plugin_loader,
cached_registry: Registry::get_static(), cached_registry: Registry::get_static(),
@ -98,6 +106,7 @@ impl Server {
public_key, public_key,
cached_server_brand, cached_server_brand,
private_key, private_key,
command_dispatcher: Arc::new(command_dispatcher),
status_response, status_response,
status_response_json, status_response_json,
public_key_der, public_key_der,