From 288c45537c14e83c7f9cabaa8cdea3052e711c99 Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Mon, 2 Feb 2026 04:35:02 -0700 Subject: [PATCH] Get suggestions working --- .../patchbukkit/entity/PatchBukkitEntity.java | 10 +-- .../patchbukkit/entity/PatchBukkitPlayer.java | 53 +++++++++---- rust/src/commands/mod.rs | 49 ++++++++++-- rust/src/events/join.rs | 4 +- rust/src/java/jvm/commands.rs | 6 +- rust/src/java/jvm/worker.rs | 11 +-- rust/src/java/plugin/command_manager/mod.rs | 74 +++++-------------- 7 files changed, 109 insertions(+), 98 deletions(-) diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java index d2ada73..b2abfaa 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java @@ -53,6 +53,7 @@ public class PatchBukkitEntity implements Entity { protected final UUID uuid; private final String name; private static PermissibleBase perm; + private boolean visibleByDefault = true; public PatchBukkitEntity( UUID uuid, @@ -511,8 +512,7 @@ public class PatchBukkitEntity implements Entity { @Override public @NotNull Server getServer() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getServer'"); + return Bukkit.getServer(); } @Override @@ -678,14 +678,12 @@ public class PatchBukkitEntity implements Entity { @Override public void setVisibleByDefault(boolean visible) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setVisibleByDefault'"); + this.visibleByDefault = visible; } @Override public boolean isVisibleByDefault() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'isVisibleByDefault'"); + return this.visibleByDefault; } @Override diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java index 150786c..40393aa 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java @@ -1,11 +1,14 @@ package org.patchbukkit.entity; +import java.lang.ref.WeakReference; import java.net.InetAddress; import java.net.InetSocketAddress; import java.time.Duration; import java.time.Instant; import java.util.Collection; import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -83,6 +86,8 @@ public class PatchBukkitPlayer extends PatchBukkitHumanEntity implements Player { + private final Map>> invertedVisibilityEntities = new HashMap<>(); + public PatchBukkitPlayer(UUID uuid, String name) { super(uuid, name); } @@ -1213,26 +1218,42 @@ public class PatchBukkitPlayer @Override public boolean canSee(Player player) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'canSee'"); - } - - @Override - public void hideEntity(Plugin plugin, Entity entity) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'hideEntity'"); - } - - @Override - public void showEntity(Plugin plugin, Entity entity) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'showEntity'"); + return this.canSee((org.bukkit.entity.Entity) player); } @Override public boolean canSee(Entity entity) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'canSee'"); + return this.equals(entity) || entity.isVisibleByDefault() ^ this.invertedVisibilityEntities.containsKey(entity.getUniqueId()); + } + + @Override + public void hideEntity(Plugin plugin, Entity entity) { + Preconditions.checkNotNull(plugin, "Plugin cannot be null"); + Preconditions.checkNotNull(entity, "Entity cannot be null"); + if (this.equals(entity)) return; + + Set> plugins = invertedVisibilityEntities + .computeIfAbsent(entity.getUniqueId(), k -> new HashSet<>()); + plugins.add(new WeakReference<>(plugin)); + } + + @Override + public void showEntity(Plugin plugin, Entity entity) { + Preconditions.checkNotNull(plugin, "Plugin cannot be null"); + Preconditions.checkNotNull(entity, "Entity cannot be null"); + if (this.equals(entity)) return; + + Set> plugins = invertedVisibilityEntities.get(entity.getUniqueId()); + if (plugins == null) return; + + plugins.removeIf(ref -> { + Plugin p = ref.get(); + return p == null || p.equals(plugin); + }); + + if (plugins.isEmpty()) { + invertedVisibilityEntities.remove(entity.getUniqueId()); + } } @Override diff --git a/rust/src/commands/mod.rs b/rust/src/commands/mod.rs index a99af9e..e5592f5 100644 --- a/rust/src/commands/mod.rs +++ b/rust/src/commands/mod.rs @@ -1,7 +1,10 @@ +use std::borrow::Cow; + use pumpkin::{ command::{ CommandExecutor, CommandSender, args::{Arg, ArgumentConsumer, GetClientSideArgParser, SuggestResult}, + tree::{CommandTree, builder::argument}, }, entity::EntityBase, }; @@ -12,6 +15,8 @@ use tokio::sync::{mpsc, oneshot}; use crate::java::jvm::commands::{JvmCommand, Location, Rotation}; +const ARG_ANY: &str = "any"; + pub struct JavaCommandExecutor { pub cmd_name: String, pub command_tx: mpsc::Sender, @@ -25,7 +30,6 @@ pub enum SimpleCommandSender { } pub struct AnyCommandNode { - command_name: String, command_tx: mpsc::Sender, } @@ -86,7 +90,6 @@ impl ArgumentConsumer for AnyCommandNode { None }; - let command_name = self.command_name.clone(); let command_sender: SimpleCommandSender = sender.into(); Box::pin(async move { @@ -94,15 +97,15 @@ impl ArgumentConsumer for AnyCommandNode { self.command_tx .send(JvmCommand::GetCommandTabComplete { command_sender, - cmd_name: command_name, + full_command: input.to_string(), respond_to: tx, - args: input.split(' ').map(|arg| arg.to_string()).collect(), location, }) .await .unwrap(); - rx.await.unwrap() + let suggestion = rx.await.unwrap(); + suggestion }) } } @@ -125,16 +128,20 @@ impl CommandExecutor for JavaCommandExecutor { &'a self, sender: &'a pumpkin::command::CommandSender, _server: &'a pumpkin::server::Server, - _args: &'a pumpkin::command::args::ConsumedArgs<'a>, + args: &'a pumpkin::command::args::ConsumedArgs<'a>, ) -> pumpkin::command::CommandResult<'a> { Box::pin(async move { + let full_command = match args.get(ARG_ANY) { + Some(Arg::Msg(msg)) => format!("/{} {}", self.cmd_name, msg), + _ => format!("/{}", self.cmd_name), + }; + let (tx, _rx) = oneshot::channel(); self.command_tx .send(JvmCommand::TriggerCommand { - cmd_name: self.cmd_name.clone(), + full_command: full_command, respond_to: tx, command_sender: sender.into(), - args: vec![self.cmd_name.clone()], }) .await .unwrap(); @@ -142,3 +149,29 @@ impl CommandExecutor for JavaCommandExecutor { }) } } + +pub fn init_java_command( + cmd_name: impl Into, + command_tx: mpsc::Sender, + names: impl IntoIterator>, + description: impl Into>, +) -> CommandTree { + let cmd_name = cmd_name.into(); + CommandTree::new(names, description) + .execute(JavaCommandExecutor { + cmd_name: cmd_name.clone(), + command_tx: command_tx.clone(), + }) + .then( + argument( + ARG_ANY, + AnyCommandNode { + command_tx: command_tx.clone(), + }, + ) + .execute(JavaCommandExecutor { + cmd_name, + command_tx, + }), + ) +} diff --git a/rust/src/events/join.rs b/rust/src/events/join.rs index f49de10..dcfe704 100644 --- a/rust/src/events/join.rs +++ b/rust/src/events/join.rs @@ -17,14 +17,14 @@ pub struct PatchBukkitJoinHandler { impl EventHandler for PatchBukkitJoinHandler { fn handle_blocking<'a>( &self, - server: &Arc, + _server: &Arc, event: &'a mut PlayerJoinEvent, ) -> BoxFuture<'a, ()> { let command_tx = self.command_tx.clone(); Box::pin(async move { let event: &mut PlayerJoinEvent = event; - let (tx, rx) = oneshot::channel(); + let (tx, _rx) = oneshot::channel(); let sent_event = event.clone(); command_tx .send(JvmCommand::TriggerEvent { diff --git a/rust/src/java/jvm/commands.rs b/rust/src/java/jvm/commands.rs index 7a5363c..de26c8a 100644 --- a/rust/src/java/jvm/commands.rs +++ b/rust/src/java/jvm/commands.rs @@ -46,16 +46,14 @@ pub enum JvmCommand { respond_to: oneshot::Sender>, }, TriggerCommand { - cmd_name: String, + full_command: String, command_sender: SimpleCommandSender, respond_to: oneshot::Sender>, - args: Vec, }, GetCommandTabComplete { command_sender: SimpleCommandSender, - cmd_name: String, + full_command: String, respond_to: oneshot::Sender>, CommandError>>, - args: Vec, location: Option, }, } diff --git a/rust/src/java/jvm/worker.rs b/rust/src/java/jvm/worker.rs index 760f504..2430acf 100644 --- a/rust/src/java/jvm/worker.rs +++ b/rust/src/java/jvm/worker.rs @@ -230,10 +230,9 @@ impl JvmWorker { } } JvmCommand::TriggerCommand { - cmd_name, + full_command, command_sender, respond_to, - args, } => { let jvm = match self.jvm { Some(ref jvm) => jvm, @@ -242,15 +241,14 @@ impl JvmWorker { let result = self.command_manager - .trigger_command(jvm, &cmd_name, command_sender, args); + .trigger_command(jvm, full_command, command_sender); let _ = respond_to.send(result); } JvmCommand::GetCommandTabComplete { command_sender, - cmd_name, + full_command, respond_to, - args, location, } => { let jvm = match self.jvm { @@ -261,8 +259,7 @@ impl JvmWorker { let result = self.command_manager.get_tab_complete( jvm, command_sender, - &cmd_name, - args, + full_command, location, ); diff --git a/rust/src/java/plugin/command_manager/mod.rs b/rust/src/java/plugin/command_manager/mod.rs index 61620f5..90bd858 100644 --- a/rust/src/java/plugin/command_manager/mod.rs +++ b/rust/src/java/plugin/command_manager/mod.rs @@ -1,20 +1,14 @@ use std::sync::{Arc, Mutex}; use anyhow::Result; -use j4rs::{Instance, InvocationArg, JavaClass, Jvm}; -use pumpkin::{ - command::{ - dispatcher::CommandError, - tree::{CommandTree, builder::literal}, - }, - plugin::Context, -}; +use j4rs::{Instance, InvocationArg, Jvm}; +use pumpkin::{command::dispatcher::CommandError, plugin::Context}; use pumpkin_protocol::java::client::play::CommandSuggestion; use pumpkin_util::permission::{Permission, PermissionDefault}; use tokio::sync::mpsc; use crate::{ - commands::{JavaCommandExecutor, SimpleCommandSender}, + commands::{SimpleCommandSender, init_java_command}, config, java::{ jvm::commands::{JvmCommand, Location}, @@ -36,6 +30,10 @@ impl CommandManager { jvm.invoke_static("org.bukkit.Bukkit", "getServer", InvocationArg::empty())?; let command_map = jvm.invoke(&server_instance, "getCommandMap", InvocationArg::empty())?; + let command_map = jvm.cast( + &command_map, + "org.patchbukkit.command.PatchBukkitCommandMap", + )?; self.command_map = Some(command_map); @@ -46,11 +44,10 @@ impl CommandManager { &mut self, jvm: &Jvm, sender: SimpleCommandSender, - cmd_name: &str, - args: Vec, + full_command: String, location: Option, ) -> Result>, CommandError> { - match self.try_tab_complete(jvm, sender, cmd_name, args, location) { + match self.try_tab_complete(jvm, sender, full_command, location) { Ok(suggestions) => Ok(suggestions), Err(e) => { log::warn!("Tab completion failed: {e}"); @@ -63,8 +60,7 @@ impl CommandManager { &mut self, jvm: &Jvm, sender: SimpleCommandSender, - cmd_name: &str, - args: Vec, + full_command: String, location: Option, ) -> Result>> { let command_map = match self.command_map { @@ -112,8 +108,7 @@ impl CommandManager { "tabComplete", &[ InvocationArg::try_from(sender)?, - InvocationArg::try_from(cmd_name)?, - InvocationArg::try_from(jvm.java_list(JavaClass::String, args)?)?, + InvocationArg::try_from(full_command)?, InvocationArg::try_from(location)?, ], )? @@ -123,8 +118,7 @@ impl CommandManager { "tabComplete", &[ InvocationArg::try_from(sender)?, - InvocationArg::try_from(cmd_name)?, - InvocationArg::try_from(jvm.java_list(JavaClass::String, args)?)?, + InvocationArg::try_from(full_command)?, ], )? }; @@ -181,43 +175,14 @@ impl CommandManager { ], )?; } - let j_sender = jvm - .invoke_static( - "org.bukkit.Bukkit", - "getConsoleSender", - InvocationArg::empty(), - ) - .map_err(|e| e.to_string()) - .unwrap(); - // Make the tab working, at least the first thingy - let dispatch_result = jvm - .invoke( - command_map, - "tabComplete", - &[ - InvocationArg::from(j_sender), - InvocationArg::try_from(format!("{} ", &cmd_name)).unwrap(), - ], - ) - .unwrap(); - - let tab_list: Vec = jvm.to_rust(dispatch_result).unwrap(); - - let mut node = CommandTree::new( + let node = init_java_command( + cmd_name.clone(), + command_tx.clone(), [&cmd_name], cmd_data.description.clone().unwrap_or_default(), - ) - .execute(JavaCommandExecutor { - cmd_name: cmd_name.clone(), - command_tx: command_tx.clone(), - }); - for tab in tab_list { - node = node.then(literal(tab).execute(JavaCommandExecutor { - cmd_name: cmd_name.clone(), - command_tx: command_tx.clone(), - })); - } + ); + // TODO // let permission = if let Some(perm) = cmd_data.permission.clone() { // perm @@ -243,9 +208,8 @@ impl CommandManager { pub fn trigger_command( &mut self, jvm: &Jvm, - cmd_name: &str, + full_command: String, sender: SimpleCommandSender, - _args: Vec, ) -> Result<()> { let command_map = match self.command_map { Some(ref command_map) => command_map, @@ -262,7 +226,7 @@ impl CommandManager { "dispatch", &[ InvocationArg::from(j_sender), - InvocationArg::try_from(cmd_name)?, + InvocationArg::try_from(full_command)?, ], )?;