From c842ceab81d68cb8517b66bfdab16028c0dfeca0 Mon Sep 17 00:00:00 2001 From: "./slyph" <118862741+slyphmp4@users.noreply.github.com> Date: Mon, 24 Aug 2026 07:48:13 +0300 Subject: [PATCH 1/2] Fix Mob target handling for Minecraft 26.2 --- .../mixin/world/entity/MobMixin.java | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/cardboardpowered/mixin/world/entity/MobMixin.java b/src/main/java/org/cardboardpowered/mixin/world/entity/MobMixin.java index a96faae1..5567c598 100644 --- a/src/main/java/org/cardboardpowered/mixin/world/entity/MobMixin.java +++ b/src/main/java/org/cardboardpowered/mixin/world/entity/MobMixin.java @@ -23,17 +23,14 @@ public abstract class MobMixin extends LivingEntity implements MobBridge, Entity @Nullable public LivingEntity target; - @Shadow - public abstract @Nullable LivingEntity getTarget(); - protected MobMixin(EntityType entityType, Level level) { super(entityType, level); } @Inject(method = "setTarget", at = @At("HEAD"), cancellable = true) - public void setTargetCraftBukkit(LivingEntity livingEntity, CallbackInfo ci) { - // CraftBukkit start - fire event - boolean set = this.cardboard$setTarget(target, EntityTargetEvent.TargetReason.UNKNOWN); + public void setTargetCraftBukkit(@Nullable LivingEntity livingEntity, CallbackInfo ci) { + // CraftBukkit start - fire event for the target Minecraft is actually trying to set. + boolean set = this.cardboard$setTarget(livingEntity, EntityTargetEvent.TargetReason.UNKNOWN); if (set) { // Let the other mods call their @Inject if set is false. ci.cancel(); } @@ -44,36 +41,46 @@ public void setTargetCraftBukkit(LivingEntity livingEntity, CallbackInfo ci) { new java.util.concurrent.atomic.AtomicBoolean(false); @Override - public boolean cardboard$setTarget(@Nullable LivingEntity target, EntityTargetEvent.@Nullable TargetReason reason) { - if (this.getTarget() == target) { + public boolean cardboard$setTarget(@Nullable LivingEntity newTarget, EntityTargetEvent.@Nullable TargetReason reason) { + // Use the raw target field here. In 26.2 Mob#getTarget() can apply validity checks, + // while Bukkit needs to compare against the actual target currently stored by Minecraft. + LivingEntity oldTarget = this.target; + if (oldTarget == newTarget) { return false; } + if (reason != null) { - if (reason == EntityTargetEvent.TargetReason.UNKNOWN && this.getTarget() != null && target == null) { - reason = this.getTarget().isAlive() ? EntityTargetEvent.TargetReason.FORGOT_TARGET : EntityTargetEvent.TargetReason.TARGET_DIED; + if (reason == EntityTargetEvent.TargetReason.UNKNOWN && oldTarget != null && newTarget == null) { + reason = oldTarget.isAlive() + ? EntityTargetEvent.TargetReason.FORGOT_TARGET + : EntityTargetEvent.TargetReason.TARGET_DIED; } if (reason == EntityTargetEvent.TargetReason.UNKNOWN && cardboard$warnedUnknownTarget.compareAndSet(false, true)) { - // Fires on every generic setTarget call, i.e. constantly once mobs are active. - // Report it once per run so the signal survives without flooding the log. - ((ServerLevelBridge)this.level()).getCraftServer().getLogger().log(java.util.logging.Level.WARNING, + // Some generic target acquisitions still do not expose a Bukkit reason. + // Report only the first occurrence so useful diagnostics remain without log spam. + ((ServerLevelBridge) this.level()).getCraftServer().getLogger().log(java.util.logging.Level.WARNING, "Unknown target reason, please report on the issue tracker (further occurrences suppressed)", new Exception()); } - CraftLivingEntity ctarget = null; - if (target != null) { - ctarget = (CraftLivingEntity) target.getBukkitEntity(); + + CraftLivingEntity craftTarget = null; + if (newTarget != null) { + craftTarget = (CraftLivingEntity) newTarget.getBukkitEntity(); } - org.bukkit.event.entity.EntityTargetLivingEntityEvent event = new org.bukkit.event.entity.EntityTargetLivingEntityEvent(this.getBukkitEntity(), ctarget, reason); + + org.bukkit.event.entity.EntityTargetLivingEntityEvent event = + new org.bukkit.event.entity.EntityTargetLivingEntityEvent(this.getBukkitEntity(), craftTarget, reason); if (!event.callEvent()) { return false; } if (event.getTarget() != null) { - target = ((CraftLivingEntity) event.getTarget()).getHandle(); + newTarget = ((CraftLivingEntity) event.getTarget()).getHandle(); } else { - target = null; + newTarget = null; } } - this.target = target; + + this.target = newTarget; return true; // CraftBukkit end } From 9ccc782fb0f3b753afe9e24e5b78bcbcf12987de Mon Sep 17 00:00:00 2001 From: "./slyph" <118862741+slyphmp4@users.noreply.github.com> Date: Tue, 25 Aug 2026 04:47:19 +0300 Subject: [PATCH 2/2] Combine mob target fix with 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..3d689e8d 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 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 + * 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()); + RootCommandNode filteredRoot = new RootCommandNode<>(); + for (CommandNode node : rootCommandNode.getChildren()) { + if (event.getCommands().contains(node.getName())) { + filteredRoot.addChild(node); + } } - PlayerCommandSendEvent event = - new PlayerCommandSendEvent( - (Player) ((ServerPlayerBridge) entityplayer) - .getBukkitEntity(), - new LinkedHashSet<>(bukkit) - ); + entityplayer.connection.send( + new ClientboundCommandsPacket(filteredRoot, COMMAND_NODE_INSPECTOR) + ); - CraftEventFactory.callEvent(event); + ci.cancel(); } -} \ No newline at end of file +}