Skip to content

Commit

Permalink
Merge pull request #39 from user622628252416/master
Browse files Browse the repository at this point in the history
Command Parser
  • Loading branch information
Snowiiii authored Aug 20, 2024
2 parents 66ce63b + fd544b5 commit 589d258
Show file tree
Hide file tree
Showing 12 changed files with 706 additions and 136 deletions.
59 changes: 59 additions & 0 deletions pumpkin/src/commands/arg_player.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use crate::client::Client;
use crate::commands::dispatcher::InvalidTreeError;
use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError;
use crate::commands::tree::{ConsumedArgs, RawArgs};
use crate::commands::CommandSender;
use crate::commands::CommandSender::Player;

/// todo: implement (so far only own name + @s/@p is implemented)
pub fn consume_arg_player(src: &CommandSender, args: &mut RawArgs) -> Option<String> {
let s = args.pop()?;

match s {
"@s" if src.is_player() => Some(s.into()),
"@p" if src.is_player() => Some(s.into()),
"@r" => None, // todo: implement random player target selector
"@a" | "@e" => None, // todo: implement all players target selector
_ => {
// todo: implement any other player than sender
if let Player(client) = src {
if let Some(profile) = &client.gameprofile {
if profile.name == s {
return Some(s.into());
};
};
};
None
}
}
}

/// todo: implement (so far only own name + @s/@p is implemented)
pub fn parse_arg_player<'a>(
src: &'a mut CommandSender,
arg_name: &str,
consumed_args: &ConsumedArgs,
) -> Result<&'a mut Client, InvalidTreeError> {
let s = consumed_args
.get(arg_name)
.ok_or(InvalidConsumptionError(None))?
.as_str();

match s {
"@s" if src.is_player() => Ok(src.as_mut_player().unwrap()),
"@p" if src.is_player() => Ok(src.as_mut_player().unwrap()),
"@r" => Err(InvalidConsumptionError(Some(s.into()))), // todo: implement random player target selector
"@a" | "@e" => Err(InvalidConsumptionError(Some(s.into()))), // todo: implement all players target selector
_ => {
// todo: implement any other player than sender
if let Player(client) = src {
if let Some(profile) = &client.gameprofile {
if profile.name == s {
return Ok(client);
};
};
};
Err(InvalidConsumptionError(Some(s.into())))
}
}
}
96 changes: 96 additions & 0 deletions pumpkin/src/commands/cmd_gamemode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use std::str::FromStr;

use num_traits::FromPrimitive;
use pumpkin_text::TextComponent;

use crate::commands::arg_player::{consume_arg_player, parse_arg_player};

use crate::commands::dispatcher::InvalidTreeError;
use crate::commands::dispatcher::InvalidTreeError::{
InvalidConsumptionError, InvalidRequirementError,
};
use crate::commands::tree::{CommandTree, ConsumedArgs, RawArgs};
use crate::commands::tree_builder::{argument, require};
use crate::commands::CommandSender;
use crate::commands::CommandSender::Player;
use crate::entity::player::GameMode;

pub(crate) const NAME: &str = "gamemode";

const DESCRIPTION: &str = "Change a player's gamemode.";

const ARG_GAMEMODE: &str = "gamemode";
const ARG_TARGET: &str = "target";

pub fn consume_arg_gamemode(_src: &CommandSender, args: &mut RawArgs) -> Option<String> {
let s = args.pop()?;

if let Ok(id) = s.parse::<u8>() {
match GameMode::from_u8(id) {
None | Some(GameMode::Undefined) => {}
Some(_) => return Some(s.into()),
};
};

match GameMode::from_str(s) {
Err(_) | Ok(GameMode::Undefined) => None,
Ok(_) => Some(s.into()),
}
}

pub fn parse_arg_gamemode(consumed_args: &ConsumedArgs) -> Result<GameMode, InvalidTreeError> {
let s = consumed_args
.get(ARG_GAMEMODE)
.ok_or(InvalidConsumptionError(None))?;

if let Ok(id) = s.parse::<u8>() {
match GameMode::from_u8(id) {
None | Some(GameMode::Undefined) => {}
Some(gamemode) => return Ok(gamemode),
};
};

match GameMode::from_str(s) {
Err(_) | Ok(GameMode::Undefined) => Err(InvalidConsumptionError(Some(s.into()))),
Ok(gamemode) => Ok(gamemode),
}
}

pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(DESCRIPTION).with_child(
require(&|sender| sender.permission_lvl() >= 2).with_child(
argument(ARG_GAMEMODE, consume_arg_gamemode)
.with_child(
require(&|sender| sender.is_player()).execute(&|sender, args| {
let gamemode = parse_arg_gamemode(args)?;

return if let Player(target) = sender {
target.set_gamemode(gamemode);
target.send_system_message(TextComponent::text(&format!(
"Game mode was set to {:?}",
gamemode
)));

Ok(())
} else {
Err(InvalidRequirementError)
};
}),
)
.with_child(
argument(ARG_TARGET, consume_arg_player).execute(&|sender, args| {
let gamemode = parse_arg_gamemode(args)?;
let target = parse_arg_player(sender, ARG_TARGET, args)?;

target.set_gamemode(gamemode);
target.send_system_message(TextComponent::text(&format!(
"Set own game mode to {:?}",
gamemode
)));

Ok(())
}),
),
),
)
}
74 changes: 74 additions & 0 deletions pumpkin/src/commands/cmd_help.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError;
use crate::commands::dispatcher::{CommandDispatcher, InvalidTreeError};
use crate::commands::tree::{CommandTree, ConsumedArgs, RawArgs};
use crate::commands::tree_builder::argument;
use crate::commands::{dispatcher_init, CommandSender, DISPATCHER};
use pumpkin_text::TextComponent;

pub(crate) const NAME: &str = "help";
pub(crate) const ALIAS: &str = "?";

const DESCRIPTION: &str = "Print a help message.";

const ARG_COMMAND: &str = "command";

fn consume_arg_command(_src: &CommandSender, args: &mut RawArgs) -> Option<String> {
let s = args.pop()?;

let dispatcher = DISPATCHER.get_or_init(dispatcher_init);

if dispatcher.commands.contains_key(s) {
Some(s.into())
} else {
None
}
}

fn parse_arg_command<'a>(
consumed_args: &'a ConsumedArgs,
dispatcher: &'a CommandDispatcher,
) -> Result<(&'a str, &'a CommandTree<'a>), InvalidTreeError> {
let command_name = consumed_args
.get(ARG_COMMAND)
.ok_or(InvalidConsumptionError(None))?;

if let Some(tree) = dispatcher.commands.get::<&str>(&command_name.as_str()) {
Ok((command_name, tree))
} else {
Err(InvalidConsumptionError(Some(command_name.into())))
}
}

pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(DESCRIPTION)
.with_child(
argument(ARG_COMMAND, consume_arg_command).execute(&|sender, args| {
let dispatcher = DISPATCHER.get_or_init(dispatcher_init);

let (name, tree) = parse_arg_command(args, dispatcher)?;

sender.send_message(TextComponent::text(&format!(
"{} - {} Usage:{}",
name,
tree.description,
tree.paths_formatted(name)
)));

Ok(())
}),
)
.execute(&|sender, _args| {
let dispatcher = DISPATCHER.get_or_init(dispatcher_init);

for (name, tree) in &dispatcher.commands {
sender.send_message(TextComponent::text(&format!(
"{} - {} Usage:{}",
name,
tree.description,
tree.paths_formatted(name)
)));
}

Ok(())
})
}
22 changes: 22 additions & 0 deletions pumpkin/src/commands/cmd_pumpkin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::server::CURRENT_MC_VERSION;
use pumpkin_protocol::CURRENT_MC_PROTOCOL;
use pumpkin_text::{color::NamedColor, TextComponent};

use crate::commands::tree::CommandTree;

pub(crate) const NAME: &str = "pumpkin";

const DESCRIPTION: &str = "Display information about Pumpkin.";

pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(DESCRIPTION).execute(&|sender, _| {
let version = env!("CARGO_PKG_VERSION");
let description = env!("CARGO_PKG_DESCRIPTION");

sender.send_message(TextComponent::text(
&format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")
).color_named(NamedColor::Green));

Ok(())
})
}
13 changes: 13 additions & 0 deletions pumpkin/src/commands/cmd_stop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use crate::commands::tree::CommandTree;
use crate::commands::tree_builder::require;

pub(crate) const NAME: &str = "stop";

const DESCRIPTION: &str = "Stop the server.";

pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(DESCRIPTION).with_child(
require(&|sender| sender.permission_lvl() >= 4)
.execute(&|_sender, _args| std::process::exit(0)),
)
}
102 changes: 102 additions & 0 deletions pumpkin/src/commands/dispatcher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use crate::commands::dispatcher::InvalidTreeError::{
InvalidConsumptionError, InvalidRequirementError,
};
use crate::commands::tree::{CommandTree, ConsumedArgs, NodeType, RawArgs};
use crate::commands::CommandSender;
use std::collections::HashMap;

#[derive(Debug)]
pub(crate) enum InvalidTreeError {
/// This error means that there was an error while parsing a previously consumed argument.
/// That only happens when consumption is wrongly implemented, as it should ensure parsing may
/// never fail.
InvalidConsumptionError(Option<String>),

/// Return this if a condition that a [Node::Require] should ensure is met is not met.
InvalidRequirementError,
}

pub(crate) struct CommandDispatcher<'a> {
pub(crate) commands: HashMap<&'a str, CommandTree<'a>>,
}

/// Stores registered [CommandTree]s and dispatches commands to them.
impl<'a> CommandDispatcher<'a> {
/// Execute a command using its corresponding [CommandTree].
pub(crate) fn dispatch(&'a self, src: &mut CommandSender, cmd: &str) -> Result<(), String> {
let mut parts = cmd.split_ascii_whitespace();
let key = parts.next().ok_or("Empty Command")?;
let raw_args: Vec<&str> = parts.rev().collect();

let tree = self.commands.get(key).ok_or("Command not found")?;

// try paths until fitting path is found
for path in tree.iter_paths() {
match Self::try_is_fitting_path(src, path, tree, raw_args.clone()) {
Err(InvalidConsumptionError(s)) => {
println!("Error while parsing command \"{cmd}\": {s:?} was consumed, but couldn't be parsed");
return Err("Internal Error (See logs for details)".into());
}
Err(InvalidRequirementError) => {
println!("Error while parsing command \"{cmd}\": a requirement that was expected was not met.");
return Err("Internal Error (See logs for details)".into());
}
Ok(is_fitting_path) => {
if is_fitting_path {
return Ok(());
}
}
}
}

Err(format!(
"Invalid Syntax. Usage:{}",
tree.paths_formatted(key)
))
}

fn try_is_fitting_path(
src: &mut CommandSender,
path: Vec<usize>,
tree: &CommandTree,
mut raw_args: RawArgs,
) -> Result<bool, InvalidTreeError> {
let mut parsed_args: ConsumedArgs = HashMap::new();

for node in path.iter().map(|&i| &tree.nodes[i]) {
match node.node_type {
NodeType::ExecuteLeaf { run } => {
return if raw_args.is_empty() {
run(src, &parsed_args)?;
Ok(true)
} else {
Ok(false)
};
}
NodeType::Literal { string, .. } => {
if raw_args.pop() != Some(string) {
return Ok(false);
}
}
NodeType::Argument {
consumer: consume,
name,
..
} => {
if let Some(consumed) = consume(src, &mut raw_args) {
parsed_args.insert(name, consumed);
} else {
return Ok(false);
}
}
NodeType::Require { predicate, .. } => {
if !predicate(src) {
return Ok(false);
}
}
}
}

Ok(false)
}
}
Loading

0 comments on commit 589d258

Please sign in to comment.