feat(command): add dialog command

This commit is contained in:
Alexander Medvedev 2026-07-14 14:08:57 +02:00
parent a877d16aeb
commit 75d1bc5a88
6 changed files with 196 additions and 3 deletions

View File

@ -2,7 +2,26 @@ use pumpkin_util::text::TextComponent;
use serde::Serialize;
#[derive(Serialize)]
pub struct DialogNBT<'a>(pub &'a Dialog);
pub struct DialogNBT<'a>(pub DialogNBTSource<'a>);
impl<'a> DialogNBT<'a> {
#[must_use]
pub const fn from_dialog(dialog: &'a Dialog) -> Self {
Self(DialogNBTSource::Struct(dialog))
}
#[must_use]
pub const fn from_nbt(compound: &'a pumpkin_nbt::compound::NbtCompound) -> Self {
Self(DialogNBTSource::Nbt(compound))
}
}
#[derive(Serialize)]
#[serde(untagged)]
pub enum DialogNBTSource<'a> {
Struct(&'a Dialog),
Nbt(&'a pumpkin_nbt::compound::NbtCompound),
}
#[derive(Serialize)]
pub struct Dialog {

View File

@ -0,0 +1,57 @@
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::identifier::Identifier;
use crate::command::{
argument_types::{
FromStringReader,
argument_type::{ArgumentType, JavaClientArgumentType},
nbt::NbtCompoundArgumentType,
},
context::command_context::CommandContext,
errors::command_syntax_error::CommandSyntaxError,
string_reader::StringReader,
};
#[derive(Clone, Debug, PartialEq)]
pub enum DialogArg {
Id(Identifier),
Nbt(NbtCompound),
}
pub struct DialogArgumentType;
impl ArgumentType for DialogArgumentType {
type Item = DialogArg;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
if reader.peek() == Some('{') {
let compound = NbtCompoundArgumentType.parse(reader)?;
Ok(DialogArg::Nbt(compound))
} else {
let id = Identifier::from_reader(reader)?;
Ok(DialogArg::Id(id))
}
}
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
JavaClientArgumentType::Dialog
}
fn examples(&self) -> Vec<String> {
examples!(
"minecraft:server_links",
"custom:my_dialog",
"{type:\"minecraft:notice\",title:\"Hello\"}"
)
}
}
impl DialogArgumentType {
/// Returns the parsed [`DialogArg`] from the name of the argument.
pub fn get<'a>(
context: &'a CommandContext,
name: &'_ str,
) -> Result<&'a DialogArg, CommandSyntaxError> {
context.get_argument(name)
}
}

View File

@ -221,6 +221,7 @@ pub mod attribute;
pub mod block;
pub mod coordinates;
pub mod core;
pub mod dialog;
pub mod entity;
pub mod entity_anchor;
pub mod entity_selector;

View File

@ -0,0 +1,110 @@
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
use crate::command::argument_types::dialog::{DialogArg, DialogArgumentType};
use crate::command::argument_types::entity::EntityArgumentType;
use crate::command::context::command_context::CommandContext;
use crate::command::errors::error_types::LiteralCommandErrorType;
use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult};
use pumpkin_protocol::IdOr;
use pumpkin_protocol::java::client::dialog::DialogNBT;
use pumpkin_protocol::java::client::play::{CPlayClearDialog, CPlayShowDialog};
use pumpkin_util::PermissionLvl;
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
use pumpkin_util::text::TextComponent;
const DESCRIPTION: &str = "Manages player dialog screens.";
const PERMISSION: &str = "minecraft:command.dialog";
const ARG_TARGETS: &str = "targets";
const ARG_DIALOG: &str = "dialog";
struct DialogClearExecutor;
impl CommandExecutor for DialogClearExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let targets = EntityArgumentType::get_players(context, ARG_TARGETS).await?;
let count = targets.len();
let packet = CPlayClearDialog::new();
for player in &targets {
player.client.send_packet_now(&packet).await;
}
let msg = if count == 1 {
TextComponent::text(format!(
"Cleared dialog for {}",
targets[0].gameprofile.name
))
} else {
TextComponent::text(format!("Cleared dialogs for {count} players"))
};
context.source.send_feedback(msg, true).await;
Ok(count as i32)
})
}
}
static REGISTRY_ERROR: LiteralCommandErrorType = LiteralCommandErrorType::new(
"Registry-defined dialogs are not yet supported. Please specify the dialog inline using SNBT.",
);
struct DialogShowExecutor;
impl CommandExecutor for DialogShowExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let targets = EntityArgumentType::get_players(context, ARG_TARGETS).await?;
let dialog_arg = DialogArgumentType::get(context, ARG_DIALOG)?;
match dialog_arg {
DialogArg::Nbt(compound) => {
let count = targets.len();
let dialog_nbt = DialogNBT::from_nbt(compound);
let packet = CPlayShowDialog::new(IdOr::Value(dialog_nbt));
for player in &targets {
player.client.send_packet_now(&packet).await;
}
let msg = if count == 1 {
TextComponent::text(format!(
"Showed dialog to {}",
targets[0].gameprofile.name
))
} else {
TextComponent::text(format!("Showed dialog to {count} players"))
};
context.source.send_feedback(msg, true).await;
Ok(count as i32)
}
DialogArg::Id(_id) => Err(REGISTRY_ERROR.create_without_context()),
}
})
}
}
pub fn register(dispatcher: &mut CommandDispatcher, registry: &mut PermissionRegistry) {
registry.register_permission_or_panic(Permission::new(
PERMISSION,
DESCRIPTION,
PermissionDefault::Op(PermissionLvl::Two),
));
dispatcher.register(
command("dialog", DESCRIPTION)
.requires(PERMISSION)
.then(literal("clear").then(
argument(ARG_TARGETS, EntityArgumentType::Players).executes(DialogClearExecutor),
))
.then(
literal("show").then(
argument(ARG_TARGETS, EntityArgumentType::Players).then(
argument(ARG_DIALOG, DialogArgumentType).executes(DialogShowExecutor),
),
),
),
);
}

View File

@ -18,6 +18,7 @@ mod damage;
mod data;
pub mod defaultgamemode;
mod deop;
mod dialog;
mod difficulty;
mod effect;
mod enchant;
@ -165,6 +166,7 @@ pub async fn default_dispatcher(
banlist::register(&mut dispatcher, registry);
difficulty::register(&mut dispatcher, registry);
dialog::register(&mut dispatcher, registry);
help::register(&mut dispatcher, registry);
kill::register(&mut dispatcher, registry);
op::register(&mut dispatcher, registry);

View File

@ -1763,7 +1763,9 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
client
.send_packet_now(
&pumpkin_protocol::java::client::config::CConfigShowDialog::new(
pumpkin_protocol::IdOr::Value(DialogNBT(&protocol_dialog)),
pumpkin_protocol::IdOr::Value(DialogNBT::from_dialog(
&protocol_dialog,
)),
),
)
.await;
@ -1772,7 +1774,9 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
client
.send_packet_now(
&pumpkin_protocol::java::client::play::CPlayShowDialog::new(
pumpkin_protocol::IdOr::Value(DialogNBT(&protocol_dialog)),
pumpkin_protocol::IdOr::Value(DialogNBT::from_dialog(
&protocol_dialog,
)),
),
)
.await;