Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -46,6 +48,10 @@ public class CommandsMixin {
@Shadow
public com.mojang.brigadier.CommandDispatcher<CommandSourceStack> dispatcher;

@Shadow
@Final
private static ClientboundCommandsPacket.NodeInspector<CommandSourceStack> COMMAND_NODE_INSPECTOR;

@Shadow
private static <S> void fillUsableCommands(
CommandNode<S> root,
Expand Down Expand Up @@ -81,58 +87,58 @@ private static <S> 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<CommandSourceStack>, CommandNode<CommandSourceStack>> map =
Maps.newIdentityHashMap();

RootCommandNode vanillaRoot = new RootCommandNode();

RootCommandNode<CommandSourceStack> vanilla =
entityplayer.level()
.getServer()
.getCommands()
.getDispatcher()
.getRoot();

map.put(vanilla, vanillaRoot);

fillUsableCommands(
vanilla,
vanillaRoot,
entityplayer.createCommandSourceStack(),
(Map) map
);

RootCommandNode<CommandSourceStack> rootcommandnode =
RootCommandNode<CommandSourceStack> 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<String> bukkit = new LinkedHashSet<>();
Collection<String> originalCommands = new LinkedHashSet<>();
for (CommandNode<CommandSourceStack> 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<CommandSourceStack> filteredRoot = new RootCommandNode<>();
for (CommandNode<CommandSourceStack> 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();
}
}
}
47 changes: 27 additions & 20 deletions src/main/java/org/cardboardpowered/mixin/world/entity/MobMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<? extends LivingEntity> 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();
}
Expand All @@ -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
}
Expand Down
Loading