
Список изменений
Allium v0.2.7a
Wiki: https://github.com/castledking/Allium/wiki
Highlights
- New pluggable command permission resolution pipeline — The ad-hoc permission heuristics in
CommandManagerhave been replaced by a dedicatedcodes.castled.allium.permissions.commandpackage. A chain of framework-specific adapters (PaperBasicCommand, Brigadier, NightCore, Bukkit, LuckPerms, registered-permission conventions) resolves whether a player may run a command, and reports how the decision was made. Unit-tested with JUnit 5. - Vault Chat abstract-class support — When the Vault Chat class is an abstract class rather than an interface, the provider is now assigned directly instead of being skipped, so chat formatting works with permission plugins that expose Chat through an abstract base.
- Deleted chat messages no longer reappear — Fixed duplicate per-player message tracking that broke content matching in
deleteMessage, causing deleted messages to resurface on resend. - Nickname formatting fixes —
%allium_nickname%is now recognized as a player placeholder in chat formats, and channel messages use the formatted nickname (falling back to the real name when no nickname is stored). - Oraxen → Nexo migration — Built-in custom items (Tree Axe, Spawner Changer) now reference
nexo:item models instead oforaxen:.
Technical Details
Command Permission Resolver (permissions/command)
CommandManager previously resolved permissions through a single hasPermissionForCommand() method that had accumulated special cases (VanillaCommandWrapper class-name checks, Paper-internals safety nets, Allium-derived permissions). That logic is now factored into an adapter pipeline:
DefaultCommandPermissionResolver
├── (extension point: CommandAPI, ACF, cloud, …)
├── PaperBasicCommandPermissionAdapter — reflects the wrapped Paper BasicCommand, calls canUse()/permission()
├── BrigadierCommandPermissionAdapter — finds the CommandNode field, walks requires() predicates; VanillaCommandWrapper without an inspectable node → allow (defer to native dispatch)
├── NightCoreCommandPermissionAdapter — reads NightCore's public command tree reflectively (no hard dependency), tests root and sub-command nodes
├── BukkitCommandPermissionAdapter — explicit command.getPermission() → testPermissionSilent()
├── LuckPermsCommandPermissionAdapter — queries CachedPermissionData against candidate permission strings (reflective, no hard dependency)
└── RegisteredCommandPermissionAdapter — checks candidate permissions registered with Bukkit's PluginManager (wildcards only count as a grant, never as a denial)
Key contracts:
CommandPermissionAdapter.resolve()returnsOptional.empty()when the adapter does not understand the command (next adapter is tried), or aPermissionResultwithResolutionType.UNKNOWNwhen it owns the command but cannot safely inspect its permission rule.PermissionResultis a record carryingallowed, theResolutionTypethat decided, thematchedPermission(evidence), and the resolvedCommand. Anullcommand means the command is unknown → Allium's unknown-command message.PermissionCandidatesgenerates conventional permission strings (<plugin>.<module>.command.<action>etc.) from the owning plugin, command labels/aliases, and arguments. A GUI "open" action is only inferred when the owning module actually registers…command.open, so/ah-style commands work without every no-argument command looking like "open".DefaultCommandPermissionResolvercaches the registered-permission snapshot for 2 s and accepts additional adapters via its constructor — the extension point for CommandAPI, ACF, cloud, or other frameworks.
CommandManager behavior changes:
- Command resolution (including namespaced
plugin:commandfallback) now happens inside the resolver;resolveCommandForPermissionCheck,hasPermissionForCommand, andgetDerivedPluginCommandPermissionwere removed (~130 lines of heuristics). - With
debug-mode: true, every resolution is logged:Permission resolution for /<cmd>: allowed=…, type=…, matched=…— making "why was this command blocked/allowed" answerable from the log. - The legacy aliases are retained as a final fallback:
/flypasses withallium.flyorallium.tfly,/pingwithallium.ping. - The direct
net.milkbowl.vault.permission.Permissionimport was dropped; Vault permission setup remains reflective.
Tests: CommandPermissionAdaptersTest (445 lines) and PermissionCandidatesTest (126 lines) cover the adapter pipeline and candidate generation. JUnit Jupiter 5.12.2 and Surefire 3.5.2 were added to the build for this.
Vault Chat Proxy Fix
createChatProxy bailed out with null when the Vault Chat class was an abstract class instead of an interface (chatInterface.isInterface() false), which silently disabled Allium's chat formatter. It now returns the provider directly in that case — a JDK dynamic proxy is only possible for interfaces, but the concrete provider is already usable as-is.
Additionally, the "Vault Chat not available" message was upgraded from INFO to WARN and now explains the common cause: permission plugins like LuckPerms need chat: true in their config to provide chat services — Vault alone does not provide chat formatting.
Chat Message Deletion Fix
FormatChatListener used to build its own ChatMessageManager.ChatMessage and track it for every online player, in parallel with PacketChatTrackerImpl (packet capture). The listener-built entries were rendered from a different format template, so their plain text did not match the components stored by AlliumChannelManager. Content matching in deleteMessage then failed for those entries, they stayed undeleted, and the "deleted" message reappeared on resend. The listener-side tracking was removed; PacketChatTrackerImpl is now the single owner of per-player chat history.
Also in FormatChatListener, hover components are now wrapped explicitly with HoverEvent.showText(...) instead of passing the component directly to hoverEvent().
Nickname Handling
%allium_nickname%in chat format templates is now substituted with the player component (same treatment as<player>/<display_name>), in bothFormatChatListenerandAlliumChannelManager.AlliumChannelManagerno longer returns the raw stored nickname: an empty/null stored nickname falls back toplayer.getName(), and a present one is passed throughgetFormattedNickname(player, stored)so nickname formatting (colors, prefix rules) applies in channel messages.
/back Cache Invalidation (TP)
Teleporting a player now removes their entry from locationCache, so the next /back re-evaluates from lastLocation/the database instead of a stale cached location.
Adventure Component Builders
ComponentBuilder.build() calls were replaced with asComponent() in Help, Notes, and Warp (deprecation cleanup; behavior unchanged).
Oraxen → Nexo
TreeAxeItem, SpawnerChangerItem, and customitems.yml now reference nexo:tree_axe / nexo:spawner_changer item models instead of the oraxen: namespace.
Build
- Version bumped
0.2.6a→0.2.7a. - Added
org.junit.jupiter:junit-jupiter:5.12.2(test scope) andmaven-surefire-plugin:3.5.2.
