chore(command): reimplemented /difficulty (#1924)

This commit is contained in:
Laptop59
2026-03-31 00:21:05 +05:30
committed by GitHub
parent 637019dec8
commit 5a0db0f50d
4 changed files with 90 additions and 66 deletions

View File

@@ -25,6 +25,32 @@ pub enum Difficulty {
Hard = 3,
}
impl Difficulty {
/// Gets the lowercase name of this difficulty.
/// For example, [`Difficulty::Peaceful`] will yield `"peaceful"`.
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Peaceful => "peaceful",
Self::Easy => "easy",
Self::Normal => "normal",
Self::Hard => "hard",
}
}
/// Gets the translation key of this difficulty.
/// For example, [`Difficulty::Peaceful`] will yield `"options.difficulty.peaceful"`.
#[must_use]
pub const fn translation_key(self) -> &'static str {
match self {
Self::Peaceful => "options.difficulty.peaceful",
Self::Easy => "options.difficulty.easy",
Self::Normal => "options.difficulty.normal",
Self::Hard => "options.difficulty.hard",
}
}
}
impl FromStr for Difficulty {
type Err = ParseDifficultyError;

View File

@@ -98,15 +98,19 @@ pub async fn send_c_commands_packet(
.map(|i| i.try_into().expect("i32 limit reached for ids"));
// TODO:
//
// As stated in the previous TODO, after
// we can get a reference to an Arc of Server,
// we can add the permission checking.
//
// Luckily, for now the new dispatcher
// only has the /help commands which
// is accessible to everyone by default.
let satisfies_requirements = true;
// Right now, we incorrectly assume that
// requirements are always satisfied. Hopefully
// this can be fixed once the `/op` and `/deop` commands
// are reimplemented with the Arcs, after which we can uncomment
// the following line instead of the current one:
//
// let satisfies_requirements = node.requirements().evaluate(&source).await;
let satisfies_requirements = true;
match node {
AttachedNode::Root(_) => {

View File

@@ -1,37 +1,35 @@
use crate::command::CommandResult;
use crate::command::args::difficulty::DifficultyArgumentConsumer;
use crate::command::args::{Arg, GetCloned};
use crate::command::dispatcher::CommandError::{self, InvalidConsumption};
use crate::command::tree::builder::argument;
use crate::command::{CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree};
use crate::command::argument_builder::{ArgumentBuilder, command, literal};
use crate::command::context::command_context::CommandContext;
use crate::command::errors::error_types::CommandErrorType;
use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult};
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
use pumpkin_util::text::TextComponent;
use pumpkin_util::{Difficulty, PermissionLvl};
const NAMES: [&str; 1] = ["difficulty"];
const DESCRIPTION: &str = "Query or change the difficulty of the world.";
const PERMISSION: &str = "minecraft:command.difficulty";
const DESCRIPTION: &str = "Change the difficulty of the world.";
pub const ARG_DIFFICULTY: &str = "difficulty";
const FAILURE_ERROR_TYPE: CommandErrorType<1> =
CommandErrorType::new("commands.difficulty.failure");
struct DifficultyQueryExecutor;
impl CommandExecutor for DifficultyQueryExecutor {
fn execute<'a>(
&'a self,
sender: &'a CommandSender,
server: &'a crate::server::Server,
_args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a> {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let difficulty = server.get_difficulty();
let difficulty_string = format!("{difficulty:?}").to_lowercase();
let translation_key = format!("options.difficulty.{difficulty_string}");
let difficulty = context.server().get_difficulty();
sender
.send_message(TextComponent::translate(
"commands.difficulty.query",
[TextComponent::translate(translation_key, [])],
))
context
.source
.send_feedback(
TextComponent::translate(
"commands.difficulty.query",
[TextComponent::translate(difficulty.translation_key(), [])],
),
false,
)
.await;
Ok(difficulty as i32)
@@ -39,41 +37,34 @@ impl CommandExecutor for DifficultyQueryExecutor {
}
}
struct DifficultySetExecutor;
struct DifficultySetExecutor(Difficulty);
impl CommandExecutor for DifficultySetExecutor {
fn execute<'a>(
&'a self,
sender: &'a CommandSender,
server: &'a crate::server::Server,
args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a> {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let Some(Arg::Difficulty(difficulty)) = args.get_cloned(&ARG_DIFFICULTY) else {
return Err(InvalidConsumption(Some(ARG_DIFFICULTY.into())));
};
let difficulty_string = format!("{difficulty:?}").to_lowercase();
let translation_key = format!("options.difficulty.{difficulty_string}");
let difficulty = self.0;
let server = context.server();
{
let level_info = server.level_info.load();
if level_info.difficulty == difficulty {
return Err(CommandError::CommandFailed(TextComponent::translate(
"commands.difficulty.failure",
[TextComponent::translate(translation_key, [])],
)));
return Err(FAILURE_ERROR_TYPE
.create_without_context(TextComponent::text(difficulty.name())));
}
}
server.set_difficulty(difficulty, true).await;
sender
.send_message(TextComponent::translate(
"commands.difficulty.success",
[TextComponent::translate(translation_key, [])],
))
context
.source
.send_feedback(
TextComponent::translate(
"commands.difficulty.success",
[TextComponent::translate(difficulty.translation_key(), [])],
),
true,
)
.await;
Ok(0)
@@ -81,9 +72,22 @@ impl CommandExecutor for DifficultySetExecutor {
}
}
#[must_use]
pub fn init_command_tree() -> CommandTree {
CommandTree::new(NAMES, DESCRIPTION)
.execute(DifficultyQueryExecutor)
.then(argument(ARG_DIFFICULTY, DifficultyArgumentConsumer).execute(DifficultySetExecutor))
pub fn register(dispatcher: &mut CommandDispatcher, registry: &mut PermissionRegistry) {
registry
.register_permission(Permission::new(
PERMISSION,
DESCRIPTION,
PermissionDefault::Op(PermissionLvl::Two),
))
.expect("Permission should have registered successfully");
dispatcher.register(
command("difficulty", DESCRIPTION)
.requires(PERMISSION)
.then(literal("peaceful").executes(DifficultySetExecutor(Difficulty::Peaceful)))
.then(literal("easy").executes(DifficultySetExecutor(Difficulty::Easy)))
.then(literal("normal").executes(DifficultySetExecutor(Difficulty::Normal)))
.then(literal("hard").executes(DifficultySetExecutor(Difficulty::Hard)))
.executes(DifficultyQueryExecutor),
);
}

View File

@@ -113,10 +113,6 @@ pub async fn default_dispatcher(
dispatcher.register(say::init_command_tree(), "minecraft:command.say");
dispatcher.register(gamemode::init_command_tree(), "minecraft:command.gamemode");
dispatcher.register(gamerule::init_command_tree(), "minecraft:command.gamerule");
dispatcher.register(
difficulty::init_command_tree(),
"minecraft:command.difficulty",
);
dispatcher.register(
stopsound::init_command_tree(),
"minecraft:command.stopsound",
@@ -161,6 +157,7 @@ pub async fn default_dispatcher(
wrapper_dispatcher
};
difficulty::register(&mut dispatcher, registry);
help::register(&mut dispatcher, registry);
seed::register(&mut dispatcher, registry);
stop::register(&mut dispatcher, registry);
@@ -382,13 +379,6 @@ fn register_level_2_permissions(registry: &mut PermissionRegistry) {
PermissionDefault::Op(PermissionLvl::Two),
))
.unwrap();
registry
.register_permission(Permission::new(
"minecraft:command.difficulty",
"Sets the difficulty of the world",
PermissionDefault::Op(PermissionLvl::Two),
))
.unwrap();
registry
.register_permission(Permission::new(
"minecraft:command.data",