From 5221dfe30e4ef12ed7d581bff27b5baf4cc5d782 Mon Sep 17 00:00:00 2001 From: "./slyph" <118862741+slyphmp4@users.noreply.github.com> Date: Mon, 24 Aug 2026 06:16:59 +0300 Subject: [PATCH 1/2] Fix PlayerCommandSendEvent command tree filtering --- .../mixin/commands/CommandsMixin.java | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/cardboardpowered/mixin/commands/CommandsMixin.java b/src/main/java/org/cardboardpowered/mixin/commands/CommandsMixin.java index 60b26986..fb5c799b 100644 --- a/src/main/java/org/cardboardpowered/mixin/commands/CommandsMixin.java +++ b/src/main/java/org/cardboardpowered/mixin/commands/CommandsMixin.java @@ -20,6 +20,7 @@ import net.minecraft.commands.CommandBuildContext; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; +import net.minecraft.network.protocol.game.ClientboundCommandsPacket; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.permissions.PermissionProviderCheck; @@ -30,6 +31,7 @@ import org.cardboardpowered.bridge.commands.PermissionProviderCheckBridge; import org.cardboardpowered.bridge.server.level.ServerPlayerBridge; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -46,6 +48,10 @@ public class CommandsMixin { @Shadow public com.mojang.brigadier.CommandDispatcher dispatcher; + @Shadow + @Final + private static ClientboundCommandsPacket.NodeInspector COMMAND_NODE_INSPECTOR; + @Shadow private static void fillUsableCommands( CommandNode root, @@ -81,58 +87,58 @@ private static void fillUsableCommands( } } + /** + * Build the command tree for this specific player, expose its top-level + * labels through Bukkit's PlayerCommandSendEvent, then actually apply any + * removals plugins made to the event before the Brigadier tree is sent to + * the client. + * + * Cardboard previously fired PlayerCommandSendEvent but ignored changes to + * event.getCommands(), which made command-hiding plugins unable to remove + * commands from client-side slash/TAB suggestions. + */ @SuppressWarnings({"rawtypes", "unchecked"}) - @Inject(at = @At("HEAD"), method = "sendCommands") - public void bukkitize( + @Inject(at = @At("HEAD"), method = "sendCommands", cancellable = true) + private void cardboard$filterAndSendCommands( ServerPlayer entityplayer, CallbackInfo ci ) { Map, CommandNode> map = Maps.newIdentityHashMap(); - RootCommandNode vanillaRoot = new RootCommandNode(); - - RootCommandNode vanilla = - entityplayer.level() - .getServer() - .getCommands() - .getDispatcher() - .getRoot(); - - map.put(vanilla, vanillaRoot); - - fillUsableCommands( - vanilla, - vanillaRoot, - entityplayer.createCommandSourceStack(), - (Map) map - ); - - RootCommandNode rootcommandnode = + RootCommandNode rootCommandNode = new RootCommandNode<>(); - map.put(this.dispatcher.getRoot(), rootcommandnode); + map.put(this.dispatcher.getRoot(), rootCommandNode); fillUsableCommands( this.dispatcher.getRoot(), - rootcommandnode, + rootCommandNode, entityplayer.createCommandSourceStack(), (Map) map ); - Collection bukkit = new LinkedHashSet<>(); + Collection originalCommands = new LinkedHashSet<>(); + for (CommandNode node : rootCommandNode.getChildren()) { + originalCommands.add(node.getName()); + } + + PlayerCommandSendEvent event = new PlayerCommandSendEvent( + (Player) ((ServerPlayerBridge) entityplayer).getBukkitEntity(), + new LinkedHashSet<>(originalCommands) + ); + CraftEventFactory.callEvent(event); - for (CommandNode node : rootcommandnode.getChildren()) { - bukkit.add(node.getName()); + for (String command : originalCommands) { + if (!event.getCommands().contains(command)) { + rootCommandNode.removeCommand(command); + } } - PlayerCommandSendEvent event = - new PlayerCommandSendEvent( - (Player) ((ServerPlayerBridge) entityplayer) - .getBukkitEntity(), - new LinkedHashSet<>(bukkit) - ); + entityplayer.connection.send( + new ClientboundCommandsPacket(rootCommandNode, COMMAND_NODE_INSPECTOR) + ); - CraftEventFactory.callEvent(event); + ci.cancel(); } -} \ No newline at end of file +} From b616dfb324f26eb98776f1c1488f5591607957db Mon Sep 17 00:00:00 2001 From: "./slyph" <118862741+slyphmp4@users.noreply.github.com> Date: Mon, 24 Aug 2026 06:33:03 +0300 Subject: [PATCH 2/2] Build filtered Brigadier root without mutating command tree --- .../mixin/commands/CommandsMixin.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/cardboardpowered/mixin/commands/CommandsMixin.java b/src/main/java/org/cardboardpowered/mixin/commands/CommandsMixin.java index fb5c799b..3d689e8d 100644 --- a/src/main/java/org/cardboardpowered/mixin/commands/CommandsMixin.java +++ b/src/main/java/org/cardboardpowered/mixin/commands/CommandsMixin.java @@ -89,9 +89,8 @@ private static void fillUsableCommands( /** * Build the command tree for this specific player, expose its top-level - * labels through Bukkit's PlayerCommandSendEvent, then actually apply any - * removals plugins made to the event before the Brigadier tree is sent to - * the client. + * labels through Bukkit's PlayerCommandSendEvent, then send a root that + * contains only labels retained by plugins. * * Cardboard previously fired PlayerCommandSendEvent but ignored changes to * event.getCommands(), which made command-hiding plugins unable to remove @@ -129,14 +128,15 @@ private static void fillUsableCommands( ); CraftEventFactory.callEvent(event); - for (String command : originalCommands) { - if (!event.getCommands().contains(command)) { - rootCommandNode.removeCommand(command); + RootCommandNode filteredRoot = new RootCommandNode<>(); + for (CommandNode node : rootCommandNode.getChildren()) { + if (event.getCommands().contains(node.getName())) { + filteredRoot.addChild(node); } } entityplayer.connection.send( - new ClientboundCommandsPacket(rootCommandNode, COMMAND_NODE_INSPECTOR) + new ClientboundCommandsPacket(filteredRoot, COMMAND_NODE_INSPECTOR) ); ci.cancel();