JorelAli / CommandAPI

A Bukkit/Spigot API for the command UI introduced in Minecraft 1.13

Home Page:https://commandapi.jorel.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Players cannot run Entity executors

willkroboth opened this issue · comments

CommandAPI version

9.4.2

Minecraft version

1.19.4

Are you shading the CommandAPI?

Yes

What I did

I registered the following command with CommandAPI 9.4.2 and 8.8.0 (whose latest supported version is 1.19.4):

new CommandAPICommand("test")
        .executesEntity((entity, args) -> {
            Bukkit.getServer().broadcastMessage(entity + " ran the command");
        })
        .register();

What actually happened

When using 9.4.2 (plugin jar: 9.4.2.zip), running /test as a player will result in the message This command has no implementations for craftplayer:

9 4 2

What should have happened

When using 8.8.0 (plugin jar: 8.8.0.zip), running /test as a player runs the code defined in executesEntity:

8 8 0

Server logs and CommandAPI config

No response

Other

I believe this change was introduced in 9.0.0 due to #378. That changed this code in CommandAPIExecutor like so:

8.8.0 (focus line 104):

private int execute(List<? extends IExecutorTyped> executors, CommandSender sender, Object[] args)
throws WrapperCommandSyntaxException {
if (isForceNative()) {
return execute(executors, sender, args, ExecutorType.NATIVE);
} else if (sender instanceof Player && matches(executors, ExecutorType.PLAYER)) {
return execute(executors, sender, args, ExecutorType.PLAYER);
} else if (sender instanceof Entity && matches(executors, ExecutorType.ENTITY)) {
return execute(executors, sender, args, ExecutorType.ENTITY);
} else if (sender instanceof ConsoleCommandSender && matches(executors, ExecutorType.CONSOLE)) {
return execute(executors, sender, args, ExecutorType.CONSOLE);
} else if (sender instanceof BlockCommandSender && matches(executors, ExecutorType.BLOCK)) {
return execute(executors, sender, args, ExecutorType.BLOCK);
} else if (sender instanceof ProxiedCommandSender && matches(executors, ExecutorType.PROXY)) {
return execute(executors, sender, args, ExecutorType.PROXY);
} else if (matches(executors, ExecutorType.ALL)) {
return execute(executors, sender, args, ExecutorType.ALL);
} else {
throw new WrapperCommandSyntaxException(new SimpleCommandExceptionType(
new LiteralMessage(CommandAPI.getConfiguration().getMissingImplementationMessage()
.replace("%s", sender.getClass().getSimpleName().toLowerCase())
.replace("%S", sender.getClass().getSimpleName()))).create());
}
}

9.0.0 (focus line 95):

private int execute(List<? extends TypedExecutor<CommandSender, WrapperType>> executors, ExecutionInfo<CommandSender, WrapperType> info)
throws WrapperCommandSyntaxException {
if (isForceNative()) {
return execute(executors, info, ExecutorType.NATIVE);
} else if (info.senderWrapper() instanceof AbstractPlayer && matches(executors, ExecutorType.PLAYER)) {
return execute(executors, info, ExecutorType.PLAYER);
} else if (info.senderWrapper() instanceof AbstractEntity && matches(executors, ExecutorType.ENTITY)) {
return execute(executors, info, ExecutorType.ENTITY);
} else if (info.senderWrapper() instanceof AbstractConsoleCommandSender && matches(executors, ExecutorType.CONSOLE)) {
return execute(executors, info, ExecutorType.CONSOLE);
} else if (info.senderWrapper() instanceof AbstractBlockCommandSender && matches(executors, ExecutorType.BLOCK)) {
return execute(executors, info, ExecutorType.BLOCK);
} else if (info.senderWrapper() instanceof AbstractProxiedCommandSender && matches(executors, ExecutorType.PROXY)) {
return execute(executors, info, ExecutorType.PROXY);
} else if (matches(executors, ExecutorType.ALL)) {
return execute(executors, info, ExecutorType.ALL);
} else {
throw new WrapperCommandSyntaxException(new SimpleCommandExceptionType(
new LiteralMessage(CommandAPI.getConfiguration().getMissingImplementationMessage()
.replace("%s", info.sender().getClass().getSimpleName().toLowerCase())
.replace("%S", info.sender().getClass().getSimpleName()))).create());
}
}

The relevant change here is sender instanceof Entity && matches(executors, ExecutorType.ENTITY) to info.senderWrapper() instanceof AbstractEntity && matches(executors, ExecutorType.ENTITY). In 8.8.0, a Player is an instance of Entity, while in 9.0.0, an AbstractPlayer is not an instance of AbstractEntity. Hence, the player is allowed to use the executesEntity executor in 8.8.0, but not 9.0.0 or later.


I noticed this 'issue' when I refactored the executor system in commit 62d5ea5 on the dev/command-build-rewrite branch. I unknowingly reintroduced the logic from 8.8.0 where the executor is selected using the platform-specific senders.

dev/command-build-rewrite (focus line 32)

@Override
default ExecutionInfo<Sender, Source> tryForSender(ExecutionInfo<CommandSender, Source> info) {
CommandSender sender = info.sender();
for (ExecutorType type : types()) {
// Check if we can cast to the defined sender type
if (switch (type) {
case ALL -> true;
case PLAYER -> sender instanceof Player;
case ENTITY -> sender instanceof Entity;
case CONSOLE -> sender instanceof ConsoleCommandSender;
case BLOCK -> sender instanceof BlockCommandSender;
case PROXY -> sender instanceof ProxiedCommandSender;
case NATIVE -> {
// If we're a NATIVE executor, always accept and convert sender to a NativeProxyCommandSender
NativeProxyCommandSender proxyCommandSender = CommandAPIBukkit.<Source>get().getNativeProxyCommandSender(info.cmdCtx());
info = info.copyWithNewSender(proxyCommandSender);
yield true;
}
case REMOTE -> sender instanceof RemoteConsoleCommandSender;
case FEEDBACK_FORWARDING -> {
PaperImplementations paper = CommandAPIBukkit.get().getPaper();
yield paper.isPaperPresent() && paper.getFeedbackForwardingCommandSender().isInstance(sender);
}
}) {
return (ExecutionInfo<Sender, Source>) info;
}
}
return null;
}

Hence, if you execute the same test command using this branch (plugin jar: command-build-rewrite.zip), you get the 8.8.0 behavior where the code in executesEntity is run.

I think the 8.8.0 behavior is correct. It is still possible to run different code if the sender is a player using executesPlayer.

new CommandAPICommand("test")
        .executesPlayer((player, args) -> {
            // Run if player
        })
        .executesEntity((entity, args) -> {
            // Otherwise, run if entity
        })
        .register();

If the 8.8.0 behavior is correct, then this issue is already fixed on dev/command-build-rewrite, so I think we can just wait for that to be merged. If the 9.4.2 behavior is correct, then I'll make sure to take that into account on dev/command-build-rewrite.

I'd say the 8.8.0 behaviour is correct. What we could've done for 9.0.0 is to take the inheritance into account for the wrapper classes but I'd say waiting for your command build rewrite is fine.