Hooks
Economy, permissions, placeholders and region queries behind provider-agnostic contracts, plus lazy binding for plugins that enable after you.
Presence
Hooks.isPresent("PlaceholderAPI");
The one primitive everything else is built on.
The hook registry
A hook may depend on a plugin that enables after yours, so binding cannot always happen in
onEnable.
HookRegistry hooks = new HookRegistry();
hooks.register(EconomyBridge.class, EconomyBridge.orDummy());
hooks.defer(RegionService.class, "WorldGuard", () -> WorldGuardRegionService.find().orElseThrow());
hooks.bindPresent(); // sweep plugins already enabled, at startup
hooks.onPluginEnabled(event.getPlugin().getName()); // from a PluginEnableEvent listener
Optional<RegionService> regions = hooks.get(RegionService.class);
An instance, not static state, so each plugin owns its own registry.
Economy
EconomyBridge economy = EconomyBridge.orDummy();
economy.deposit(player, 100);
economy.withdraw(player, 50);
economy.has(player, 50);
economy.balance(player);
economy.format(1234.5);
economy.isPresent();
economy.currencySymbol();
economy.currencyNameSingular();
economy.currencyNamePlural();
orDummy picks the best backend available, classic Vault then VaultUnlocked, and falls back to a
no-op DummyEconomy so a call site never null-checks. EconomyBridge.find() returns the Optional
when you want to know.
They run through whatever economy plugin is installed, and some of those hit a database synchronously. Route them off the main thread.
Async and caching wrappers
AsyncEconomy async = new AsyncEconomy(economy, scheduler);
async.balanceAsync(player).thenAccept(this::show);
async.hasAsync(player, 50);
async.withdrawAsync(player, 50);
async.depositAsync(player, 100);
EconomyBridge cached = new CachedEconomyBridge(economy, Duration.ofSeconds(5), Clock.systemUTC());
cached.invalidate(player);
cached.invalidateAll();
A cached bridge is for a menu that shows a balance in twenty slots. Invalidate after any write your own code makes.
Surviving an economy reload
RebindingEconomyBridge bridge = new RebindingEconomyBridge();
Bukkit.getPluginManager().registerEvents(new EconomyServiceListener(bridge), plugin);
Watches Bukkit's service registry and rebinds when an economy plugin registers or unregisters, so a
/plugman reload of Vault does not leave your plugin holding a dead provider.
Permissions and ranks
LuckPermsHook.find().ifPresent(lp -> {
lp.prefix(player);
lp.suffix(player);
lp.primaryGroup(player);
lp.metaValue(player, "town");
lp.groups(player);
lp.groupExists("vip");
});
Offline lookups are asynchronous, because they are not cached and must not block:
lp.prefixAsync(uuid);
lp.suffixAsync(uuid);
lp.primaryGroupAsync(uuid);
lp.metaValueAsync(uuid, "town");
The Vault permission bridge covers the generic case:
VaultPermission.find().ifPresent(perms -> {
perms.has(player, "myplugin.use");
perms.add(player, "myplugin.trial");
perms.remove(player, "myplugin.trial");
perms.primaryGroup(player);
});
Placeholders
Reading is a pass-through that does nothing harmful without PlaceholderAPI installed:
Placeholders.isAvailable();
String text = Placeholders.apply(player, "Hi %player_name%");
Writing exposes your own placeholders:
PlaceholderRegistry registry = new PlaceholderRegistry()
.register("homes", (player, params) -> String.valueOf(homeCount(player)));
PlaceholderExpansions.register(registry, "UXPLIMA", "1.0.0");
PlaceholderExpansions.register("myplugin", registry, "UXPLIMA", "1.0.0");
Your placeholders are then %uxm_homes_<params>%, or %myplugin_homes_<params>% with the explicit
identifier form. Registration returns false rather than throwing when PlaceholderAPI is absent.
Regions
One contract over WorldGuard and Towny, so your code does not care which is installed, or whether either is.
RegionHooks regions = new RegionHooks();
WorldGuardRegionService.find().ifPresent(regions::register);
TownyRegionService.find().ifPresent(regions::register);
boolean canBuild = regions.active()
.map(service -> service.canBuild(player, location))
.orElse(true);
| Method | Question |
|---|---|
canBuild(player, location) | May they build here |
canInteract(player, location) | May they interact here |
regionsAt(location) | Which regions cover this point |
isWilderness(location) | Is this unclaimed |
isAvailable() | Is the backing plugin still there |
pluginName() | Which provider this is |
regions.hasProvider() tells you whether anything registered. Defaulting to true when nothing did,
as above, is usually right: no region plugin means no region restrictions.
Was this page helpful?