Allium v0.2.17a
Wiki: https://github.com/castledking/Allium/wiki
Highlights
- Allium loads and runs with no other plugins installed — The plugin previously died at load with
NoClassDefFoundError: com/github/retrooper/packetevents/event/PacketListenerCommonbecause the main class named a PacketEvents type directly, and the JVM verifies that class before any "is PacketEvents installed?" check can run. Registration moved behindPacketEventsLoader, and three chat listeners that Bukkit was silently skipping on DiscordSRV-less servers now register normally. Verified on a clean Paper 26.3 server whoseplugins/folder contains nothing butAllium.jar. - Head drops are configurable in a new
spawner_heads.yml— Every mob that drops a spawner-core head is listed with its own drop chance, Looting chance and per-level bonus. Delete an entry (or setenabled: false) to stop that head dropping, or add a mob by giving it asoundandtexture. Reloads with/core reload. override-moremobs-head-dropends double head rates — With a "More Mob Heads" style datapack installed, both it and Allium dropped a head for the same mob. The new setting (defaulttrue) removes the datapack's head for any mob inspawner_heads.yml, leaving exactly one source. Set it tofalseand both drop, so a single kill can produce two heads.- Mob plushie crafting — A mob head in the center of a crafting grid with any eight wool around it makes that mob's Nexo plushie (
nexo:axolotl_plushieand friends). Datapack heads, Allium's own heads and vanilla skulls all work; all 78 plushies in the pack are reachable. - Stacked mobs drop heads — SpawnerMeta's stacked kills never fire a death event, so only the final mob in a stack could ever drop an Allium head. Stack kills are now detected directly and rolled like any other kill, multiplied by the spawner's looting upgrade.
- "Ban Player" in Discord's right-click Apps menu — Staff right-click any relayed message (chat, join, leave, death, advancement) and get a dialog for player, duration and reason, which runs a normal console ban. Gated by
discord-moderation.staff-role-idand aban-exempt-groupslist that refuses to ban owners/admins. - Custom unknown-command helper text —
hide.ymlgainedunknown-command-completion-help-message, which replaces the vanilla "Unknown or incomplete command" line the client shows while typing. The client renders that text itself, so Allium ships it as a resource-pack language override. - ModGuard stops kicking innocent players — A
/core modguard reloadleft the old probe's join listener alive, so several probes raced on the same player and each scored the others' sign echoes as a mod detection. A vanilla player was kicked for "Meteor Client" two seconds after joining.
Technical Details
Loading without any other plugin
Three separate problems, all of which stop code running before its own availability guard:
| Problem | Why it fires | Fix |
|---|---|---|
| Main class rejected | PluginStart called PacketEvents.getAPI()...registerListener(...). The JVM verifies the whole main class when Paper loads it, and the verifier must resolve PacketListenerCommon to check the argument. |
Registration moved into PacketEventsLoader.registerListener(Object). PluginStart no longer names a PacketEvents type. |
| Chat listeners skipped | Bukkit reflects over a listener's methods when registering it, and a missing parameter type makes it drop the entire class. AlliumChannelManager, SpamBlockerManager and ChatFilterManager all named DiscordSRV event types, so channels, spam blocking and chat filtering were dead without DiscordSRV. |
DiscordSRV handlers moved into nested classes, which Bukkit never scans. Bukkit reflects over declared methods too, so two private helpers and a lambda capture were retyped to Object with an internal cast. |
| SpectatorTeleport always disabled | It reflectively loaded net.survivalfun.core.commands.NV, a package name from before the rename, and failed on every server. |
Constructs NV directly. |
A missing item-provider plugin (e.g. Nexo) also tripped Harvest's validator on every single config path; those are now reported once per file. On a bare Paper 26.3 server the startup log drops from 64 error lines to 11, four of which are the legitimate "nexo has no resolver" notices.
spawner_heads.yml
The 15 head drops moved out of hardcoded Java into plugins/Allium/spawner_heads.yml. The file is authoritative: a mob that is absent or disabled drops nothing.
settings:
override-moremobs-head-drop: true
heads:
ENDERMAN:
enabled: true
chance: 0.001
looting-chance: 0.0011
looting-chance-per-level: 0.0001
sound: "entity.enderman.scream"
texture: "eyJ0ZXh0dXJlcyI6..."
sound doubles as the head's identity: Allium reads the note_block_sound component back to decide which mob a head belongs to, and every configured sound is registered with MobHeadRegistry at load, so a mob added here works for spawner crafting and plushies without a code change.
With override-moremobs-head-drop: true, an EntityDeathEvent for a configured mob has any datapack head for that same mob removed from the drops before Allium rolls its own. Mobs that are not configured are never touched, so the rest of the datapack behaves normally. The one gap is SpawnerMeta's stacked "virtual kills", which drop their loot outside the death event and are not filtered.
Telling heads apart
Head identification used the mob name inside the head's note_block_sound, but the datapack borrows sounds: mooshroom uses entity.cow.ambient, cave spider uses entity.spider.step, guardian uses the elder guardian's flop, red sheep uses the evoker's wololo. Mooshroom heads therefore counted as cow heads and cave spider heads as spider heads for spawner crafting.
Heads are now matched by the item_name the datapack stamps on them ("Red Mooshroom Head", "Cave Spider Head"), generated into spawnercraft/more-mob-heads-names.txt (210 names across 84 mobs). Allium's own heads set no item_name and still resolve through their sound.
Plushie crafting
PlushieCraftListener watches the crafting grid for a head in the middle and eight wool of any color around it, then offers the Nexo item <mob>_plushie. Three ids do not follow the pattern and are aliased: ender_dragon → enderdragon_plushie, copper_golem → copper_golem_unoxidized_plushie, zombie_nautilus → zombie_nautilus_temperate_plushie. Variants collapse to the base plushie, so all three cow climates and both mooshroom colors give a cow and a mooshroom plushie respectively. Nothing is registered as a real recipe, so the listener consumes the grid and hands over the item itself, one per click. The feature only loads when Nexo is installed and spawnercraft.plushies is true.
Heads from stacked mobs
SpawnerMeta cancels the killing blow on a stack larger than one, drops that mob's loot by hand, decrements the stack and heals the entity, so no EntityDeathEvent is ever fired. Allium now snapshots the stack size on a lethal hit (priority HIGH) and confirms at MONITOR that the hit was cancelled and the stack shrank by exactly one before rolling a head — a blow cancelled by a protection plugin leaves the stack alone and produces nothing. The killer is resolved the way SpawnerMeta does it (attacker, projectile shooter, or a tamed pet's owner) and the head is multiplied by the spawner's looting upgrade, matching how SpawnerMeta treats the final kill.
Discord "Ban Player"
DiscordSRV bundles JDA 4, which predates both message context menus and modals, so the command is registered as a global application command over raw REST with the bot's own token (DiscordSRV bulk-overwrites guild commands, which would delete it). Interactions arrive by flipping RAW_EVENTS on DiscordSRV's live JDA session; a context-menu click reaches DiscordSRV as an unknown slash command and is ignored, and a modal submit becomes a generic event nothing handles.
The dialog pre-fills the player from Allium's chat-message bridge when the message is a relayed chat line, otherwise from the embed author, webhook name or message content, and staff can correct it before submitting. Permission is checked twice (on open and on submit) against staff-role-id by role position, with Discord Administrators always allowed. ban-exempt-groups matches the target's Vault group and refuses the ban outright.
Unknown-command helper text
The red line under the chat box is built client-side from the command.unknown.command translation key, so the only way to change it is a language override. Allium writes a small pack containing that key in all 142 client locales — the selected locale is loaded over en_us, so an English-only override loses. With ResourcePackManager installed the pack is dropped into its mixer/ folder (loadbefore guarantees it lands before the merge); otherwise it is written to plugins/Allium/generated/ to merge by hand. Colors take & codes and MiniMessage, with hex rounded to the nearest of the 16 legacy colors because language files cannot render hex.
ModGuard probe false kicks
ModGuardManager.reload() builds a new probe, but unregister() only removed the PacketEvents listener and left the old instance's Bukkit join listener alive. Every reload therefore added another probe that kept sending its own ghost signs at the same position and, having no packet listener, cycled through its batches on timeout. The live probe read those foreign echoes and scored them as detections: 38 of 144 recorded hits carried another probe's fallback token.
unregister() now drops the Bukkit handlers and sets a closed flag that stops already-scheduled work. As a second layer, each sign gets its own random fallback and any echo carrying a foreign probe token — or a probe token on a keybind line — is discarded instead of scored, which also covers a late echo arriving after a timeout.
Auto-restart on Discord
Discord now gets exactly two messages per restart: one warning at auto-restart.discord-warning-seconds (default 60) and the final notice, instead of an embed per countdown step. The channel topic carries the countdown through autorestart.channel-description / channel-description-now, throttled to one edit per five minutes because Discord rate-limits channel edits to roughly two per ten minutes; the final "restarting now" topic is always forced through.
Other fixes
- Emoji in Discord-relayed names keep their own colors. Raw Unicode emoji in Discord nicknames are wrapped as
&r{emoji}{roleColor}, matching the existing:alias:handling, with longest sequences matched first so ZWJ and variation-selector emoji are not double-wrapped. - SpawnerMeta spawner delivery uses
/spawnermeta giveinstead of the old/smalias. - Handcuffs no longer spam "You caught a fish!" on every catch.
/core reloadreloadsspawner_heads.ymland re-syncs (or removes) the Discord ban command.
Verification
- Maven suite: 220 tests, 0 failures, 0 errors. New coverage for the probe echo rules, the unknown-command pack, the Discord ban command builder and exempt groups, head-name resolution and plushie ids, and
spawner_heads.ymlparsing. - Bare-server boot: Paper 26.3 with only
Allium.jarinplugins/reachesDonein ~7s, enables Allium, and registers every listener including the three chat managers. No PacketEvents, Vault, DiscordSRV or Nexo present. - Head sources: all 78 Nexo mob plushies resolve to a craftable head once the 15 configured mobs fall back to Allium's own heads.