▶️ ЗАБЕРИ СВОИ 8 ПОДАРКОВ 🎁 ПРИ СОЗДАНИИ СВОЕГО МАЙНКРАФТ СЕРВЕРА
Allium

Allium

A modern, secure Essentials solution

Оцените первым
302
2
Все версииAllium v0.2.13a

Allium v0.2.13a

Release11.08.2026

Список изменений

Allium v0.2.13a

Wiki: https://github.com/castledking/Allium/wiki

Highlights

  • Permission migration no longer fires on wildcard grants — The SFCore-era core.*allium.* migration asks Vault whether a player holds each old node. Vault answers resolved checks, not "is this node set", so a player carrying * — or an op — answered yes to every core.<node> question and had the entire permission list written onto their account as real allium.feed, allium.trash, … nodes. Handing * to someone temporarily was enough to trigger it. Migration now probes with a node nobody can hold; when that comes back true, the account resolves everything and is left alone until the blanket grant is gone. Groups had the identical defect and got the same guard.
  • Mob Disarmer and Phantom Obliterator — Two charged Nexo-backed tools. The Disarmer strips a mob's held items and armour and rolls each piece against the mob's own vanilla drop chance; the Obliterator clears unnamed phantoms within 64 blocks. Both carry three charges in the item's PDC rather than in a per-player map, so the cooldown belongs to the tool and survives trading, storage and restarts.
  • Claim handcuffs — A second handcuff family for landowners. Restraining an untrusted player inside your own claim starts a five-second countdown that ends in a GriefPrevention claim ban; dropping the cuffs cancels it, and so does the target leaving the claim. All GPExpansion access is reflective, so Allium still starts without it and works against either upstream GriefPrevention or GriefPrevention3D.
  • Chat colours are parsed once, against permissions — Legacy codes and MiniMessage tags used to fight over the same message: &#8C8F9B&lVIP was pushed through the MiniMessage parser, which printed the codes instead of applying them. Both systems now filter against their own permission sets and fold into a single MiniMessage string that is deserialized once, so one message can mix &a, &#RRGGBB and <gradient> freely.
  • One answer for how many homes a player gets — Numbered permissions and the /core sethomes override disagreed depending on which code path asked. The effective limit is now the higher of the two everywhere, /core sethomes <player> shows the breakdown, and %allium_homes_max% reports unlimited instead of a number nobody set.
  • /seen shared IPs are symmetric — Shared accounts were matched against the target's current address only, so the moment either account changed IP they stopped listing each other. The lookup now walks the full IP history.
  • Reliability%allium_nickname% parses without PlaceholderAPI or Essentials, Essentials-only nicknames are imported on join, /spy now clears targeted spying along with global, spawner cores respond to right-clicking a block, and the ModGuard translation probe stops reporting a hit when the client echoes the raw key.

Technical Details

Permission migration and blanket grants

Two migrations carry SFCore's core.* nodes forward: performPermissionMigration() walks every Vault group once at startup, and an inner PlayerPermissionMigrationListener walks each player on join until player_migration_completed is set. Both decide what to migrate with playerHas / groupHas.

Those are permission checks. LuckPerms resolves them through wildcards, and Bukkit resolves an unregistered node — which every core.<node> is, since only allium.* is declared in plugin.yml — to its OP default. A player holding *, and any op, therefore answered true to all ~120 entries in permissionsToMigrate. The migration dutifully "moved" each one and called playerAdd for its allium. equivalent, permanently writing a full permission list onto an account that had none of it. Staff handing out * to help a player with something produced exactly this.

The probe. Before any writes, the account is asked about allium.migrationprobe.<random UUID> — a node that cannot legitimately be granted. A true answer means everything resolves true and there is nothing meaningful to read, so migration is skipped:

Skipping permission migration for Steve (UUID: …): holds a wildcard/op grant that resolves every permission.

Skipped players are deliberately not marked migrated_perms = TRUE. Once the wildcard is taken away, their real nodes migrate on a later join. Groups are checked the same way with groupHas and skipped individually, so one admin group carrying * no longer poisons the whole pass.

Wildcard fan-out. A genuine core.* node was migrated to allium.* and then fell through into the per-node loop, where the not-yet-removed wildcard still answered true for core.feed, core.trash and everything else — producing the same spray of individual nodes. The per-node loop is now the else branch on both the player and group paths: a real core.* migrates on its own and nothing else is written.

SituationBeforeNow
Player holds *Every allium.<node> written explicitlySkipped, retried after the grant is removed
Player is opEvery allium.<node> written explicitlySkipped
Player holds core.*allium.* plus every individual nodeallium.* only
Player holds core.feedallium.feedallium.feed (unchanged)
Group holds *Entire list stamped onto the groupSkipped

Accounts polluted by earlier builds keep their spurious nodes; this release stops the write, it does not retract past ones.

Mob Disarmer and Phantom Obliterator

Both tools live in items/impl/, register through CustomItemRegistry, and keep every piece of state on the stack itself.

Mob DisarmerPhantom Obliterator
Base itemDiamond swordNetherite sword
UseRight-click a mobRight-click air
EffectStrips hands, then head→feetKills unnamed phantoms within 64 blocks
Charges33
Recharge120s after the last charge is spent120s
Break chance5% on uses 1–2, 25% on the lastSame
Bypassallium.adminallium.admin
Nexo idsmob_disarmer, _2-3, _1-3, _0-3 (CMD 1004–1007)phantom_obliterator, _2-3, _1-3, _0-3 (CMD 1008–1011)

Charges belong to the tool. Counts, the recharge deadline, the broken flag and a lifetime use counter live in the stack's PDC, so the cooldown travels with the item rather than with whoever last held it. Refills are all-or-nothing — the item is either charged or recharging — and a recharge that completes while the owner is offline is settled on their next join.

The item transforms in hand. Each charge count is its own Nexo item, so spending a charge rewrites the stack's item model, custom model data and both id tags (allium:custom_item_id and nexo:id) to the matching variant, and rewrites them back when the recharge lands. Because the tags match what Nexo writes, a stack handed out by /nexo give mob_disarmer_1-3 is recognised here too.

Drops follow the mob, not the tool. Each stripped piece is paired with the drop chance read off the mob before the slot is cleared — 8.5% for naturally spawned gear, 2.0 for anything the mob picked up, whatever a summon command asked for otherwise — and rolled individually. Villagers are the exception and drop everything. If a mob turns out to have nothing equipped, no charge is spent.

Neither sword can be destroyed by durability. SwordToolDurabilityListener intercepts the final durability point: the stack is left one point from breaking, flagged worn, and given stick-level attack damage via ApiCompat.ATTACK_DAMAGE (added this release for the 1.21.3 GENERIC_ATTACK_DAMAGE rename). An anvil repair clears only that combat penalty — it does not restore charges, and a broken tool, the 25% roll, stays broken.

Claim handcuffs

HandcuffsItem was rewritten from a single PDC flag into four persistent states across two families:

TypeIdFamilyCMD
StaffhandcuffsSTAFF1012
Staff, restraininghandcuffs_restrainedSTAFF1013
Claimclaim_handcuffsCLAIM1014
Claim, restrainingclaim_handcuffs_restrainedCLAIM1015

Type resolution reads Allium's id first, then Nexo's, then custom model data, and finally the legacy allium:handcuffs_item byte — cuffs from older builds become ordinary staff cuffs the first time they are rewritten. /core item give <player> claim_handcuffs issues the new family.

Staff cuffs now need a permission. allium.restrain (or allium.admin) is checked both when the rod is cast and again when the bobber lands; allium.handcuffs.resist still exempts a target. Claim cuffs need no Allium permission — the claim itself is the authority.

What claim cuffs check, through the reflective ClaimHandcuffBridge, before a restraint is allowed: the target stands inside a claim, the caster owns it or holds MANAGE trust, the target holds no trust of any kind, and the target is not already banned from it. Any failure sends the reason and nothing happens.

The countdown. A successful restraint blinds and slows both players, titles a five-second countdown at each of them, and pulses particles that tighten each second. It is abandoned if the restraint ends, if the caster drops the cuffs, or if the target leaves the claim; on completion the bridge is re-checked, the target is released and detached from the vehicle stack first, and only then is the ban written to GPExpansion's claim data store. If that write fails, the player is released and both sides are told.

plugin.yml gains GPExpansion and GriefPrevention as softdepends. Every GPX reference is reflective, so a server without them loses claim cuffs and nothing else.

Chat colour parsing

ChatColorParser is a new permission-aware parser shared by FormatChatListener and AlliumChannelManager. It accepts &a, &#RRGGBB, &x&R&R&G&G&B&B, their § equivalents, and MiniMessage tags — in the same message.

The two systems are gated separately, then merged: MiniMessage tags are validated on the text as typed, legacy codes are converted to tags afterwards, and the tags Allium generates itself are never permission-checked. That ordering is what lets a player with colour permissions but no MiniMessage permissions keep their &a codes.

NodeGrants
chat.color.<name>That single colour (chat.color.red, …)
chat.color.hex&#RRGGBB and &x hex forms
chat.color / chat.color.*Every colour, hex included
chat.format.<style>bold, italic, underline, strikethrough, magic, reset
chat.format / chat.format.*Every style
chat.minimessageMiniMessage tags at all
chat.minimessage.<tag>One tag type
chat.minimessage.*Every tag

Codes the author may not use are dropped rather than printed, and a parse failure falls back to fully stripped text instead of raw markup. Two related fixes came out of the same pass: legacy hex no longer counts as "needs the MiniMessage parser" (the bug that printed &#8C8F9B&lVIP verbatim), and the sanitiser that was meant to drop non-ASCII characters no longer erases the whole message — it previously matched [^-<DEL>].

AlliumChannelManager.sendPlayerMessage gained an overload that carries the coloured form alongside the plain one: the plain text still drives the Discord relay, the console line and duplicate suppression, while the coloured form is what renders in game.

Home limits

HomeLimits is now the single source of truth. Two systems hand out homes — numbered allium.sethome.<n> nodes and the /core sethomes database override — and the effective limit is the higher of the two, so a staff grant never silently downgrades a rank and a rank upgrade is never swallowed by a stale override. allium.sethome.unlimited and allium.sethome.* resolve to unlimited, which renders as unlimited rather than a number.

The permission side reads effective permissions directly instead of probing 100 nodes, falling back to the probe only for permission plugins that resolve numbered nodes without listing them. Overrides are cached for 5 seconds — they are read on every placeholder resolve, and each read costs two H2 round trips on the main thread — and /core sethomes invalidates the entry as it writes.

ChangeDetail
/core sethomes <player>New: shows effective limit, permission grant, override and homes set
/core sethomes <player> +N|-NNow relative to the effective limit, not to the override alone
/core sethomes permissionallium.sethomes accepted alongside allium.admin
home.delay (config)New: teleport delay for /home, previously read from teleport.delay
allium.home.nodelayNow what skips the home delay; /home incorrectly checked allium.tpa.nodelay on all three teleport paths. Not declared in plugin.yml, so grant it through your permission plugin
home.invalid-name (lang)New message; the rejection path passed no {home} placeholder

An override set below what the player's rank already grants now says so instead of appearing to take effect.

Placeholders

%allium_home% and %allium_time% are delegates of the master %allium_% expansion, and a non-null answer stops the chain. Both returned "" for parameters they did not recognise, silently swallowing every placeholder registered behind them; both now return null and let the chain continue.

PlaceholderNotes
%allium_homes_max%Effective limit or unlimited; %allium_home_max% still accepted
%allium_homes_set%Homes set; %allium_home_set% still accepted
%allium_home_<n|name>_<w|world|x|y|z|yaw|pitch>%world is new alongside w; the home_ prefix is optional
%allium_home_<n|name>_location%Unchanged
%allium_time_world_time_24h%Accepted alongside world_time_24

Home name parsing was rewritten to split on the last underscore and validate the suffix against a known field set, so names containing underscores resolve correctly instead of being mangled by a replace on the coordinate name.

%allium_nickname% and %allium_nickname_raw% are replaced natively by NicknameManager.applyAlliumPlaceholders in chat, channel formats and join/quit messages — no PlaceholderAPI, no Essentials, and no chat.placeholderapi permission required — falling back to the player's real name when no nickname is set. FormatChatListener no longer touches %gradientdisplayname%; that placeholder belongs to GradientPlus, and Allium's animated take on it stays behind %allium_gradientdisplayname%.

allium.gradientname changed meaning: the player's GradientPlus colour is now shown to everyone, and the permission buys the phase animation. Without it the same gradient is rendered static rather than being withheld. GradientNameManager reads GradientPlus through %gradient_<text>% first — that form paints arbitrary text and so works regardless of GradientPlus' name_source_placeholder — with a re-entrancy guard for configurations that point that setting back at Allium, and a 1-second colour cache so a per-tick tab rebuild does not re-resolve placeholders.

Shared-IP accounts in /seen

getPlayersSeenOnIp(ip, uuid) matched other accounts against a single address: the target's current IP when online, their last known IP when offline. Two accounts that had shared a house for months stopped listing each other as soon as either one reconnected on a different address, and the relationship was asymmetric — A could list B while B did not list A.

getSharedIpAccountNames(uuid) self-joins player_ip_history on ip_address, so any address the two accounts ever shared is a match and the result is symmetric by construction. Rows are collapsed by UUID, so an account sharing several addresses is listed once, and names resolve through player_data so a renamed account appears under its current name rather than whichever name was current when the history row was written. Results are sorted case-insensitively.

Other fixes

  • /spy said "disabled" without disabling everything. With both modes active, /spy removed the caller from global spying, reported disabled and returned — leaving their targeted entries in place, so messages kept arriving. It now clears global and targeted spying together, and remains the only entry point that can turn global spying on. Turning a target off by re-running /spy <player> requires allium.spy.others; without it the command re-affirms the target rather than toggling. Dropping one target no longer discards the whole target set, and tab completion no longer suggests the sender or anyone holding allium.spy.exempt.
  • Spawner cores ignored right-clicks on blocks. SpawnerCraftListener listened for RIGHT_CLICK_AIR only, so using a core while looking at any block did nothing. It now handles both actions at HIGHEST priority and ignores the off-hand pass so a single use does not fire twice.
  • ModGuard translation probe false positives. A client echoing the probe key back verbatim was treated as a resolved translation, i.e. a detection. The check now rejects both the fallback string and the key itself before considering the response. Separately, the bundled modguard/config.yml is used to seed the translation-probe section when it is missing, instead of rebuilding it from hard-coded defaults.
  • Essentials nickname import. A player with an Essentials nickname but no Allium one had nothing for %allium_nickname% to return. ConnectionManager now mirrors the existing sync-to-Essentials path in reverse on join, and nickname.sync-to-essentials: false disables the lookup in both directions.

Verification

The Maven suite passes with 161 tests. ChatColorParserTest is new with 15 tests covering the legacy/MiniMessage permission gates, the mixed-markup path that &#8C8F9B&lVIP <gradient:red:blue>hi</gradient> exercises, and the "ampersand that is not a colour code" case. SharedIpAccountsTest adds 5, driving the self-join SQL and the row collapsing against an in-memory H2 database, including the renamed-account and multiple-shared-IP cases. GradientNameManagerTest grew to 6 with coverage for the new static rendering path, confirming it keeps the gradient stops while dropping the phase.

The permission migration guard, the two charged tools, the claim handcuff countdown and the home limit resolution need a live server, a permission provider and (for claim cuffs) GPExpansion, and were verified in game rather than by unit test.

Файлы

Allium.jar(4.80 MiB)
Основной
Скачать

Метаданные

Канал релиза

Release

Номер версии

0.2.13a

Загрузчики

Bukkit
Folia
Paper
Purpur
Spigot

Версии игры

1.20–26.2

Загрузок

8

Дата публикации

11.08.2026

Загрузил

ID версии

Главная