Skip to main content

Permissions

Package: dev.anchorlight.stonelib.permission

PermissionService does two jobs that are worth sharing between plugins:

  • Resolve once, check cheaply. It answers "which of these hundred nodes does this player hold?" on join and caches the answer, so a render loop or a menu never has to query the permission system on every frame.
  • Grants that expire by themselves. It adds permission nodes through LuckPerms with an expiry that LuckPerms stores and enforces. A week-long event prize stays a week long across restarts, without the plugin remembering to take it back.

Setup

Lookups work on any server. Grants need the LuckPerms plugin. Add it as a soft dependency in plugin.yml:

softdepend: [LuckPerms]
PermissionService permissions = new PermissionService(getLogger());
if (!permissions.available()) {
getLogger().warning("LuckPerms is not installed; temporary rewards are disabled.");
}

StoneLib keeps every LuckPerms class out of PermissionService's own signatures, so the class loads on a server without LuckPerms. Only the grant methods need it.

Cached lookups

MethodMeaning
resolve(Player, Set<String> candidates)Checks each candidate node with hasPermission, caches the ones the player holds, and returns them. Call on join.
has(Player, String node)true from the cache if resolved and held. Otherwise falls back to a live hasPermission check.
cached(UUID)The cached set, or an empty set if nothing has been resolved.
invalidate(UUID)Drops one player's cached answer.
invalidateAll()Drops every cached answer. For a reload command.
private static final Set<String> COSMETIC_NODES = Set.of(
"edeneffects.trail.flame", "edeneffects.trail.hearts", "edeneffects.pet.wolf");

@EventHandler
public void onJoin(PlayerJoinEvent event) {
Set<String> unlocked = permissions.resolve(event.getPlayer(), COSMETIC_NODES);
menu.setUnlocked(event.getPlayer().getUniqueId(), unlocked);
}

@EventHandler
public void onQuit(PlayerQuitEvent event) {
permissions.invalidate(event.getPlayer().getUniqueId());
}

resolve uses Bukkit's hasPermission, which reads the permission data LuckPerms has already loaded for an online player. It is cheap, and does not touch LuckPerms' storage.

How far to trust the cache

  • The cache only knows about the nodes you passed to resolve.
  • has can only turn a "no" into a live check. It never answers "no" from the cache, so it cannot be wrong just because resolve has not run yet.
  • It can be out of date the other way. If a node is removed from a player after resolve, has keeps answering "yes" until the cache is invalidated. revoke invalidates for you; a change made through LuckPerms commands does not.
  • Always invalidate on quit, and re-resolve when you know a player's nodes changed.

Grants

MethodMeaning
available()Whether LuckPerms is installed and loaded.
grant(UUID, String node)Adds the node permanently.
grantTemporary(UUID, String node, Duration)Adds the node with an expiry. Throws IllegalArgumentException for a null, zero, or negative duration.
revoke(UUID, String node)Removes the node, whether it was permanent or temporary.

Each returns a CompletableFuture<Boolean> that completes with true once LuckPerms has saved the change, or false if LuckPerms is missing or the change failed. Failures are logged; the future never completes exceptionally. On success, the player's cached answer is invalidated.

permissions.grantTemporary(winner.getUniqueId(), "edeneffects.trail.crown", Duration.ofDays(7))
.thenAccept(saved -> scheduler.runSync(() -> {
if (saved) {
messages.sendNamed(winner, "reward_unlocked", "days", 7);
}
}));

The future completes on a LuckPerms thread. Hop back to the main thread, as above, before touching the player or the world.

Behaviour to know about

  • Works for offline players. Grants go through LuckPerms' user manager by UUID, so a reward can be given to someone who has logged off.
  • Applies network-wide. The expiry is stored by LuckPerms, so it survives restarts and applies on every server sharing its storage. On a network, re-resolve on each server when the player next joins.
  • Revoke is case-insensitive and removes every copy. It clears any node whose key matches, permanent or temporary, and whatever context it was set in.
  • Grants are added without contexts. A granted node applies on every server and world. For server-specific nodes, use LuckPerms directly.