feat(command): reimplement /list (#1968)

* reimplemented `/list` & new join methods for `TextComponent`

* switched to use `register_permission_or_panic`
This commit is contained in:
Laptop59
2026-04-05 15:27:55 +05:30
committed by GitHub
parent cd9068bd17
commit d78ecce368
3 changed files with 124 additions and 44 deletions

View File

@@ -12,6 +12,7 @@ use serde::de::{Error, MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
use std::borrow::Cow;
use std::fmt::Formatter;
use std::sync::LazyLock;
use style::Style;
pub mod click;
@@ -875,6 +876,52 @@ impl TextComponent {
}
}
impl TextComponent {
/// Joins multiple text components into one with a separator containing a gray comma
/// and a space after it.
///
/// # Arguments
/// - `elements` - The elements to join.
///
/// # Returns
/// The resultant text component with all the elements joined in it.
#[must_use]
pub fn join_with_comma(elements: Vec<Self>) -> Self {
static DEFAULT_SEPARATOR: LazyLock<TextComponent> = LazyLock::new(|| {
TextComponent::text(", ").color(Color::Named(color::NamedColor::Gray))
});
Self::join(elements, &DEFAULT_SEPARATOR)
}
/// Joins multiple text components into one with the given separator text component.
/// Use [`TextComponent::join_with_comma`] instead if you just want to join text components with
/// a comma in between.
///
/// # Arguments
/// - `elements` - The elements to join.
/// - `separator` - The separator to use for joining the elements provided.
///
/// # Returns
/// The resultant text component with all the elements joined in it.
#[must_use]
pub fn join(elements: Vec<Self>, separator: &Self) -> Self {
let mut result = Self::empty();
let mut first = true;
for element in elements {
if !first {
result = result.add_child(separator.clone());
}
result = result.add_child(element);
first = false;
}
result
}
}
/// The content type of the text component.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(untagged)]

View File

@@ -1,58 +1,98 @@
use std::sync::Arc;
use pumpkin_util::text::TextComponent;
use pumpkin_data::translation::{COMMANDS_LIST_NAMEANDID, COMMANDS_LIST_PLAYERS};
use pumpkin_util::{
permission::{Permission, PermissionDefault, PermissionRegistry},
text::TextComponent,
};
use crate::{
command::{
CommandExecutor, CommandResult, CommandSender, args::ConsumedArgs, tree::CommandTree,
argument_builder::{ArgumentBuilder, command, literal},
context::command_context::CommandContext,
node::{CommandExecutor, CommandExecutorResult, dispatcher::CommandDispatcher},
},
entity::player::Player,
entity::{EntityBase, EntityBaseFuture, player::Player},
};
const NAMES: [&str; 1] = ["list"];
const DESCRIPTION: &str = "Print the list of online players.";
struct Executor;
const PERMISSION: &str = "minecraft:command.list";
impl CommandExecutor for Executor {
fn execute<'a>(
&'a self,
sender: &'a CommandSender,
server: &'a crate::server::Server,
_args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a> {
enum ListMode {
Names,
Uuids,
}
struct ListCommandExecutor(ListMode);
impl CommandExecutor for ListCommandExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let players: Vec<Arc<Player>> = server.get_all_players();
let players_len = players.len() as i32;
let players: Vec<Arc<Player>> = context.server().get_all_players();
let players_len = players.len();
sender
.send_message(TextComponent::translate(
"commands.list.players",
[
TextComponent::text(players.len().to_string()),
TextComponent::text(server.basic_config.max_players.to_string()),
TextComponent::text(get_player_names(&players)),
],
))
let list = match self.0 {
ListMode::Names => get_player_names(&players).await,
ListMode::Uuids => get_player_names_and_ids(&players),
};
context
.source
.send_feedback(
TextComponent::translate(
COMMANDS_LIST_PLAYERS,
[
TextComponent::text(players_len.to_string()),
TextComponent::text(
context.server().basic_config.max_players.to_string(),
),
list,
],
),
false,
)
.await;
Ok(players_len)
Ok(players_len as i32)
})
}
}
fn get_player_names(players: &[Arc<Player>]) -> String {
let mut names = String::new();
for player in players {
if !names.is_empty() {
names.push_str(", ");
}
names.push_str(&player.gameprofile.name);
}
names
async fn get_player_names(players: &[Arc<Player>]) -> TextComponent {
let display_name_futures: Vec<EntityBaseFuture<'_, TextComponent>> =
players.iter().map(|p| p.get_display_name()).collect();
let display_names = futures::future::join_all(display_name_futures).await;
TextComponent::join_with_comma(display_names)
}
pub fn init_command_tree() -> CommandTree {
CommandTree::new(NAMES, DESCRIPTION).execute(Executor)
fn get_player_names_and_ids(players: &[Arc<Player>]) -> TextComponent {
let names_and_ids = players
.iter()
.map(|p| {
TextComponent::translate(
COMMANDS_LIST_NAMEANDID,
&[
p.get_name(),
TextComponent::text(p.gameprofile.id.to_string()),
],
)
})
.collect();
TextComponent::join_with_comma(names_and_ids)
}
pub fn register(dispatcher: &mut CommandDispatcher, registry: &mut PermissionRegistry) {
registry.register_permission_or_panic(Permission::new(
PERMISSION,
DESCRIPTION,
PermissionDefault::Allow,
));
dispatcher.register(
command("list", DESCRIPTION)
.requires(PERMISSION)
.then(literal("uuids").executes(ListCommandExecutor(ListMode::Uuids)))
.executes(ListCommandExecutor(ListMode::Names)),
);
}

View File

@@ -72,7 +72,6 @@ pub async fn default_dispatcher(
// Zero
dispatcher.register(pumpkin::init_command_tree(), "pumpkin:command.pumpkin");
dispatcher.register(list::init_command_tree(), "minecraft:command.list");
dispatcher.register(me::init_command_tree(), "minecraft:command.me");
dispatcher.register(msg::init_command_tree(), "minecraft:command.msg");
// Two
@@ -155,6 +154,7 @@ pub async fn default_dispatcher(
difficulty::register(&mut dispatcher, registry);
help::register(&mut dispatcher, registry);
list::register(&mut dispatcher, registry);
seed::register(&mut dispatcher, registry);
setidletimeout::register(&mut dispatcher, registry);
stop::register(&mut dispatcher, registry);
@@ -191,13 +191,6 @@ fn register_level_0_permissions(registry: &mut PermissionRegistry) {
PermissionDefault::Allow,
))
.unwrap();
registry
.register_permission(Permission::new(
"minecraft:command.list",
"Lists players that are currently online",
PermissionDefault::Allow,
))
.unwrap();
registry
.register_permission(Permission::new(
"minecraft:command.me",