update to latest pumpkin

This commit is contained in:
Alexander Medvedev 2026-09-05 08:15:34 +02:00
parent 9ec7a3d08e
commit 9d23556460
9 changed files with 623 additions and 561 deletions

940
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,19 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::sync::Arc;
use pumpkin::{ use pumpkin::{
command::{ command::{
CommandExecutor, CommandSender, CommandExecutor, CommandExecutorResult, CommandSender,
args::{Arg, ArgumentConsumer, GetClientSideArgParser, SuggestResult}, argument_builder::{ArgumentBuilder, CommandArgumentBuilder, argument, command},
tree::{CommandTree, builder::argument}, argument_types::core::string::StringArgumentType,
context::command_context::CommandContext,
suggestion::{
provider::SuggestionProvider,
suggestions::{Suggestions, SuggestionsBuilder},
},
}, },
entity::EntityBase, entity::EntityBase,
}; };
use pumpkin_protocol::java::client::play::{
ArgumentType, StringProtoArgBehavior, SuggestionProviders,
};
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
use crate::java::jvm::commands::{JvmCommand, Location, Rotation}; use crate::java::jvm::commands::{JvmCommand, Location, Rotation};
@ -29,48 +32,16 @@ pub enum SimpleCommandSender {
Player(String, String, bool), Player(String, String, bool),
} }
pub struct AnyCommandNode { pub struct JavaSuggestionProvider {
command_tx: mpsc::Sender<JvmCommand>, pub command_tx: mpsc::Sender<JvmCommand>,
} }
impl GetClientSideArgParser for AnyCommandNode { impl SuggestionProvider for JavaSuggestionProvider {
fn get_client_side_parser(&self) -> ArgumentType { fn suggest(&self, context: &CommandContext, mut builder: SuggestionsBuilder) -> Suggestions {
ArgumentType::String(StringProtoArgBehavior::GreedyPhrase) let sender = &context.source.output;
} let location = if let Some(world) = &context.source.world {
let position = context.source.position;
fn get_client_side_suggestion_type_override(&self) -> Option<SuggestionProviders> { let rotation = if let Some(player) = context.source.as_player() {
Some(SuggestionProviders::AskServer)
}
}
impl ArgumentConsumer for AnyCommandNode {
fn consume<'a>(
&'a self,
_sender: &'a pumpkin::command::CommandSender,
_server: &'a pumpkin::server::Server,
args: &mut pumpkin::command::tree::RawArgs<'a>,
) -> pumpkin::command::args::ConsumeResult<'a> {
let first_word = args.pop()?;
let mut msg = first_word.value.to_string();
while let Some(word) = args.pop() {
msg.push(' ');
msg.push_str(word.value);
}
Some(Arg::Msg(msg))
}
fn suggest(
&self,
sender: &pumpkin::command::CommandSender,
_server: &pumpkin::server::Server,
input: &str,
) -> SuggestResult {
let location = if let Some(position) = sender.position()
&& let Some(world) = sender.world()
{
let rotation = if let Some(player) = sender.as_player() {
let entity = player.get_entity(); let entity = player.get_entity();
let yaw = entity.yaw.load(); let yaw = entity.yaw.load();
let pitch = entity.pitch.load(); let pitch = entity.pitch.load();
@ -87,8 +58,9 @@ impl ArgumentConsumer for AnyCommandNode {
}; };
let command_sender: SimpleCommandSender = sender.into(); let command_sender: SimpleCommandSender = sender.into();
let full_command = builder.input.clone();
if let Ok(handle) = tokio::runtime::Handle::try_current() { let res = if let Ok(handle) = tokio::runtime::Handle::try_current() {
tokio::task::block_in_place(|| { tokio::task::block_in_place(|| {
handle.block_on(async move { handle.block_on(async move {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
@ -96,20 +68,17 @@ impl ArgumentConsumer for AnyCommandNode {
.command_tx .command_tx
.send(JvmCommand::GetCommandTabComplete { .send(JvmCommand::GetCommandTabComplete {
command_sender, command_sender,
full_command: input.to_string(), full_command,
respond_to: tx, respond_to: tx,
location, location,
}) })
.await .await
{ {
tracing::warn!("Failed to send tab complete to JVM worker: {e}"); tracing::warn!("Failed to send tab complete to JVM worker: {e}");
return Ok(None); return None;
} }
match rx.await { rx.await.unwrap_or(None)
Ok(res) => res,
Err(_) => Ok(None),
}
}) })
}) })
} else { } else {
@ -118,20 +87,29 @@ impl ArgumentConsumer for AnyCommandNode {
.command_tx .command_tx
.blocking_send(JvmCommand::GetCommandTabComplete { .blocking_send(JvmCommand::GetCommandTabComplete {
command_sender, command_sender,
full_command: input.to_string(), full_command,
respond_to: tx, respond_to: tx,
location, location,
}) })
{ {
tracing::warn!("Failed to send tab complete to JVM worker: {e}"); tracing::warn!("Failed to send tab complete to JVM worker: {e}");
return Ok(None); return builder.build();
} }
match rx.blocking_recv() { rx.blocking_recv().unwrap_or(None)
Ok(res) => res, };
Err(_) => Ok(None),
if let Some(suggestions) = res {
for item in suggestions {
if let Some(tooltip) = item.tooltip {
builder = builder.suggest_with_tooltip(item.suggestion, tooltip);
} else {
builder = builder.suggest(item.suggestion);
}
} }
} }
builder.build()
} }
} }
@ -154,25 +132,20 @@ impl From<&CommandSender> for SimpleCommandSender {
} }
impl CommandExecutor for JavaCommandExecutor { impl CommandExecutor for JavaCommandExecutor {
fn execute( fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
&self, if let CommandSender::Player(player) = &context.source.output {
sender: &pumpkin::command::CommandSender,
_server: &pumpkin::server::Server,
args: &pumpkin::command::args::ConsumedArgs,
) -> pumpkin::command::CommandResult {
if let CommandSender::Player(player) = sender {
crate::java::native_callbacks::utils::cache_player(player.clone()); crate::java::native_callbacks::utils::cache_player(player.clone());
} }
let full_command = match args.get(ARG_ANY) { let full_command = match context.get_argument::<String>(ARG_ANY) {
Some(Arg::Msg(msg)) => { Ok(msg) => {
if self.cmd_name.starts_with('/') { if self.cmd_name.starts_with('/') {
format!("{} {}", self.cmd_name, msg) format!("{} {}", self.cmd_name, msg)
} else { } else {
format!("/{} {}", self.cmd_name, msg) format!("/{} {}", self.cmd_name, msg)
} }
} }
_ => { Err(_) => {
if self.cmd_name.starts_with('/') { if self.cmd_name.starts_with('/') {
self.cmd_name.clone() self.cmd_name.clone()
} else { } else {
@ -181,7 +154,7 @@ impl CommandExecutor for JavaCommandExecutor {
} }
}; };
let command_sender: SimpleCommandSender = sender.into(); let command_sender: SimpleCommandSender = (&context.source.output).into();
if let Ok(handle) = tokio::runtime::Handle::try_current() { if let Ok(handle) = tokio::runtime::Handle::try_current() {
tokio::task::block_in_place(|| { tokio::task::block_in_place(|| {
@ -224,26 +197,20 @@ impl CommandExecutor for JavaCommandExecutor {
pub fn init_java_command( pub fn init_java_command(
cmd_name: impl Into<String>, cmd_name: impl Into<String>,
command_tx: mpsc::Sender<JvmCommand>, command_tx: mpsc::Sender<JvmCommand>,
names: impl IntoIterator<Item: Into<String>>,
description: impl Into<Cow<'static, str>>, description: impl Into<Cow<'static, str>>,
) -> CommandTree { ) -> CommandArgumentBuilder {
let cmd_name = cmd_name.into(); let cmd_name = cmd_name.into();
CommandTree::new(names, description) let executor = Arc::new(JavaCommandExecutor {
.execute(JavaCommandExecutor { cmd_name: cmd_name.clone(),
cmd_name: cmd_name.clone(), command_tx: command_tx.clone(),
command_tx: command_tx.clone(), });
})
command(cmd_name, description)
.executes_arc(executor.clone())
.then( .then(
argument( argument(ARG_ANY, StringArgumentType::GreedyPhrase)
ARG_ANY, .suggests(JavaSuggestionProvider { command_tx })
AnyCommandNode { .executes_arc(executor),
command_tx: command_tx.clone(),
},
)
.execute(JavaCommandExecutor {
cmd_name,
command_tx,
}),
) )
} }
@ -257,12 +224,12 @@ mod tests {
let mut dispatcher = CommandDispatcher::default(); let mut dispatcher = CommandDispatcher::default();
let (tx, _rx) = mpsc::channel(100); let (tx, _rx) = mpsc::channel(100);
let names = vec!["fly".to_string(), "/fly".to_string(), "//fly".to_string()]; let aliases = vec!["/fly".to_string(), "//fly".to_string()];
let tree = init_java_command("fly", tx, names, "Fly command"); let builder = init_java_command("fly", tx, "Fly command");
dispatcher.register(tree, "patchbukkit:command.fly"); dispatcher.register_with_aliases(builder, &aliases);
assert!(dispatcher.get_tree("fly").is_ok()); assert!(dispatcher.has_command("fly"));
assert!(dispatcher.get_tree("/fly").is_ok()); assert!(dispatcher.has_command("/fly"));
assert!(dispatcher.get_tree("//fly").is_ok()); assert!(dispatcher.has_command("//fly"));
} }
} }

View File

@ -1,7 +1,7 @@
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, sync::Arc};
use anyhow::Result; use anyhow::Result;
use pumpkin::{command::dispatcher::CommandError, plugin::Context}; use pumpkin::plugin::Context;
use pumpkin_protocol::java::client::play::CommandSuggestion; use pumpkin_protocol::java::client::play::CommandSuggestion;
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
use uuid::Uuid; use uuid::Uuid;
@ -61,7 +61,7 @@ pub enum JvmCommand {
GetCommandTabComplete { GetCommandTabComplete {
command_sender: SimpleCommandSender, command_sender: SimpleCommandSender,
full_command: String, full_command: String,
respond_to: oneshot::Sender<Result<Option<Vec<CommandSuggestion>>, CommandError>>, respond_to: oneshot::Sender<Option<Vec<CommandSuggestion>>>,
location: Option<Location>, location: Option<Location>,
}, },
} }

View File

@ -219,11 +219,11 @@ impl JvmWorker {
Ok(inner_res) => inner_res, Ok(inner_res) => inner_res,
Err(e) => { Err(e) => {
tracing::error!("Failed to get tab complete: {e}"); tracing::error!("Failed to get tab complete: {e}");
Ok(None) None
} }
} }
} else { } else {
Ok(None) None
}; };
let _ = respond_to.send(res); let _ = respond_to.send(res);

View File

@ -47,7 +47,14 @@ pub fn ffi_native_bridge_register_command_impl(request: RegisterCommandRequest)
} }
} }
let node = init_java_command(cmd_name.clone(), command_tx, names.clone(), description); let primary_name = cmd_name.trim_start_matches('/').to_string();
let alias_names: Vec<String> = names
.iter()
.filter(|n| *n != &primary_name)
.cloned()
.collect();
let node = init_java_command(primary_name.clone(), command_tx, description);
let clean_perm = cmd_name.trim_start_matches('/'); let clean_perm = cmd_name.trim_start_matches('/');
let permission = format!("patchbukkit:command.{clean_perm}"); let permission = format!("patchbukkit:command.{clean_perm}");
@ -62,7 +69,7 @@ pub fn ffi_native_bridge_register_command_impl(request: RegisterCommandRequest)
tracing::debug!("Permission '{}' registration notice: {}", permission, e); tracing::debug!("Permission '{}' registration notice: {}", permission, e);
} }
plugin_context.register_command(node, permission.clone()); plugin_context.register_command_with_aliases(node, &alias_names, permission.clone());
tracing::info!( tracing::info!(
"Successfully registered command '{}' with names {:?} and permission '{}'", "Successfully registered command '{}' with names {:?} and permission '{}'",

View File

@ -322,7 +322,7 @@ pub fn ffi_native_bridge_set_player_list_header_footer_impl(
ctx.runtime.spawn(async move { ctx.runtime.spawn(async move {
let h = pumpkin_util::text::TextComponent::from_legacy_string(&header); let h = pumpkin_util::text::TextComponent::from_legacy_string(&header);
let f = pumpkin_util::text::TextComponent::from_legacy_string(&footer); let f = pumpkin_util::text::TextComponent::from_legacy_string(&footer);
player.set_tab_list_header_footer(h, f).await; player.set_tab_list_header_footer(&h, &f);
}); });
}) })
} }

View File

@ -1,5 +1,3 @@
use pumpkin::command::args::entities::{EntitySelectorType, TargetSelector};
use crate::{ use crate::{
java::native_callbacks::CALLBACK_CONTEXT, java::native_callbacks::CALLBACK_CONTEXT,
proto::patchbukkit::common::{Location, Uuid, Vec3, World}, proto::patchbukkit::common::{Location, Uuid, Vec3, World},
@ -19,30 +17,29 @@ pub fn ffi_native_bridge_get_location_impl(entity_uuid: Uuid) -> Option<Location
let entity = ctx let entity = ctx
.plugin_context .plugin_context
.server .server
.select_entities(&TargetSelector::new(EntitySelectorType::Uuid(uuid)), None); .worlds
.load()
.iter()
.find_map(|world| world.get_entity_by_uuid(uuid))?;
if entity.len() == 1 { let entity = entity.get_entity();
let entity = entity.first().unwrap().get_entity(); let position = entity.pos.load();
let position = entity.pos.load(); let world = entity.world.load().uuid;
let world = entity.world.load().uuid; let yaw = entity.yaw.load();
let yaw = entity.yaw.load(); let pitch = entity.pitch.load();
let pitch = entity.pitch.load();
return Some(Location { Some(Location {
world: Some(World { world: Some(World {
uuid: Some(Uuid { uuid: Some(Uuid {
value: world.to_string(), value: world.to_string(),
}),
}), }),
position: Some(Vec3 { }),
x: position.x, position: Some(Vec3 {
y: position.y, x: position.x,
z: position.z, y: position.y,
}), z: position.z,
yaw, }),
pitch, yaw,
}); pitch,
} })
None
} }

View File

@ -1,4 +1,3 @@
use pumpkin::command::args::entities::{EntitySelectorType, TargetSelector};
use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_protocol::{IdOr, java::client::play::CEntitySoundEffect}; use pumpkin_protocol::{IdOr, java::client::play::CEntitySoundEffect};
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
@ -26,16 +25,15 @@ pub fn ffi_native_bridge_player_entity_play_sound_impl(
let seed = rng().random::<i64>(); let seed = rng().random::<i64>();
ctx.runtime.spawn(async move { ctx.runtime.spawn(async move {
let entity = ctx.plugin_context.server.select_entities( let entity = ctx
&TargetSelector::new(EntitySelectorType::Uuid(entity_uuid)), .plugin_context
None, .server
); .worlds
.load()
.iter()
.find_map(|world| world.get_entity_by_uuid(entity_uuid))?;
if entity.len() != 1 { let entity = entity.get_entity();
return None;
}
let entity = entity.first()?.get_entity();
player player
.send_client_packet(&CEntitySoundEffect::new( .send_client_packet(&CEntitySoundEffect::new(

View File

@ -1,6 +1,5 @@
use anyhow::Result; use anyhow::Result;
use jni::Env; use jni::Env;
use pumpkin::command::dispatcher::CommandError;
use pumpkin_protocol::java::client::play::CommandSuggestion; use pumpkin_protocol::java::client::play::CommandSuggestion;
use crate::{commands::SimpleCommandSender, java::jvm::commands::Location}; use crate::{commands::SimpleCommandSender, java::jvm::commands::Location};
@ -25,12 +24,12 @@ impl CommandManager {
sender: SimpleCommandSender, sender: SimpleCommandSender,
full_command: String, full_command: String,
location: Option<Location>, location: Option<Location>,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> { ) -> Option<Vec<CommandSuggestion>> {
match self.try_tab_complete(env, sender, full_command, location) { match self.try_tab_complete(env, sender, full_command, location) {
Ok(suggestions) => Ok(suggestions), Ok(suggestions) => suggestions,
Err(e) => { Err(e) => {
tracing::warn!("Tab completion failed: {e}"); tracing::warn!("Tab completion failed: {e}");
Ok(None) None
} }
} }
} }