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

View File

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

View File

@ -219,11 +219,11 @@ impl JvmWorker {
Ok(inner_res) => inner_res,
Err(e) => {
tracing::error!("Failed to get tab complete: {e}");
Ok(None)
None
}
}
} else {
Ok(None)
None
};
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 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);
}
plugin_context.register_command(node, permission.clone());
plugin_context.register_command_with_aliases(node, &alias_names, permission.clone());
tracing::info!(
"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 {
let h = pumpkin_util::text::TextComponent::from_legacy_string(&header);
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::{
java::native_callbacks::CALLBACK_CONTEXT,
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
.plugin_context
.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.first().unwrap().get_entity();
let position = entity.pos.load();
let world = entity.world.load().uuid;
let yaw = entity.yaw.load();
let pitch = entity.pitch.load();
let entity = entity.get_entity();
let position = entity.pos.load();
let world = entity.world.load().uuid;
let yaw = entity.yaw.load();
let pitch = entity.pitch.load();
return Some(Location {
world: Some(World {
uuid: Some(Uuid {
value: world.to_string(),
}),
Some(Location {
world: Some(World {
uuid: Some(Uuid {
value: world.to_string(),
}),
position: Some(Vec3 {
x: position.x,
y: position.y,
z: position.z,
}),
yaw,
pitch,
});
}
None
}),
position: Some(Vec3 {
x: position.x,
y: position.y,
z: position.z,
}),
yaw,
pitch,
})
}

View File

@ -1,4 +1,3 @@
use pumpkin::command::args::entities::{EntitySelectorType, TargetSelector};
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_protocol::{IdOr, java::client::play::CEntitySoundEffect};
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>();
ctx.runtime.spawn(async move {
let entity = ctx.plugin_context.server.select_entities(
&TargetSelector::new(EntitySelectorType::Uuid(entity_uuid)),
None,
);
let entity = ctx
.plugin_context
.server
.worlds
.load()
.iter()
.find_map(|world| world.get_entity_by_uuid(entity_uuid))?;
if entity.len() != 1 {
return None;
}
let entity = entity.first()?.get_entity();
let entity = entity.get_entity();
player
.send_client_packet(&CEntitySoundEffect::new(

View File

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