Allium v0.2.6a
Wiki: https://github.com/castledking/Allium/wiki
Highlights
- Paper Brigadier compatibility —
CommandManagernow correctly handles commands registered via Paper'sLifecycleEvents.COMMANDS(e.g., AbyssalLib'sCommandBus, custom Brigadier dispatcher commands). These commands appear asVanillaCommandWrapperin the command map, whosetestPermissionSilent()can false-negative. Allium now allows these commands through and defers permission evaluation to the server's native Brigadier dispatch. - Derived permission scoped to Allium-owning plugins only — The legacy heuristic that produced synthetic permissions like
<pluginName>.<commandName>is now only applied to commands owned by Allium itself. For all other plugins — whetherPluginCommand,VanillaCommandWrapper, or custom wrappers — Allium trusts the command's native permission system.
Technical Details
Problem
Commands registered through Paper's modern LifecycleEvents.COMMANDS API (used by AbyssalLib's CommandBus, among others) do not go through Bukkit's plugin.yml command registration. Instead, they register LiteralArgumentBuilder<CommandSourceStack> nodes directly into Mojang's Brigadier CommandDispatcher.
Paper wraps these dispatcher-only nodes in VanillaCommandWrapper (org.bukkit.craftbukkit.command.VanillaCommandWrapper) so they appear in SimpleCommandMap.knownCommands for Bukkit API compatibility. However, VanillaCommandWrapper.testPermissionSilent() does not reliably evaluate the underlying Brigadier node's requires() predicates, often returning false even when the player holds the correct permission.
This caused Allium's CommandManager to block all such commands and display the Allium "no-permission" message, even when the command's native permission system would have allowed execution.
Attempted Fixes (Rejected)
- Class name matching (
className.contains("Brigadier") || className.contains("Wrapper")) — fragile, case-sensitive, and missesVanillaCommandWrapperin the general case since the wrapper extendsCommand/PluginCommandvia different paths depending on the Paper version. PluginIdentifiableCommandinstanceof check — Paper'sMinecraftCommandMap$CommandWrappermay implementPluginIdentifiableCommandwith anullplugin, making the check useless.PluginCommandinstanceof check — some Paper wrapper variants extendPluginCommanddirectly, so negatingPluginCommanddoesn't reliably capture dispatcher-only commands.
Actual Fix
The fix uses a class-name-exact check for VanillaCommandWrapper as the first gate in hasPermissionForCommand():
if (className.contains("VanillaCommandWrapper")) {
return true;
}This immediately allows all Brigadier-dispatcher-registered commands and defers permission evaluation to the server's native dispatch (which correctly evaluates the CommandNode.canUse() predicates at execution time).
For PluginCommand instances, the revised logic is:
- Explicit permission set —
command.getPermission()is non-null →testPermissionSilentalready checked it, allow. - Derived permission — only computed for commands whose
PluginIdentifiableCommand.getPlugin()returns"Allium". The derived string isallium.<baseCommand>. - All other commands (
PluginCommandfrom other plugins, vanilla commands with matching Bukkit wrappers) — trusttestPermissionSilent.
getPluginForCommand Fallback Fix
The fallback in getPluginForCommand previously returned commandName.toLowerCase() when no plugin could be identified, causing the derived permission to be <commandName>.<commandName> (e.g., relique.relique). This is now null, which propagates through getDerivedPluginCommandPermission as null — treated as "no permission required".
Support and Feedback
Issues: https://github.com/castledking/Allium/issues
Wiki: https://github.com/castledking/Allium/wiki
Discord: https://discord.com/invite/pCKdCX6nYr
Website: https://castled.codes/