mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-31 08:22:33 +00:00
feat(command): reimplement /list (#1968)
* reimplemented `/list` & new join methods for `TextComponent` * switched to use `register_permission_or_panic`
This commit is contained in:
@@ -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)]
|
||||
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user