Developer API
Depend on ProVouchers, query vouchers, and react to redemptions.
ProVouchers ships a small, interface-only API (so.alaz.provouchers.api) for other
plugins: a service to query and give vouchers, and events to veto or observe
redemptions. It has been available since 0.4.0, and from the 1.0.0 release the
surface is stable and follows semantic versioning.
Depend on the API
Add the alazso repository and the provouchers-api artifact, compile-time only.
The runtime classes ship inside the installed ProVouchers plugin, so you bundle
nothing.
repositories {
maven("https://repo.alaz.so/releases") { name = "alazso" }
}
dependencies {
compileOnly("so.alaz.provouchers:provouchers-api:1.0.0")
}Declare ProVouchers as a dependency in your paper-plugin.yml so it loads first:
dependencies:
server:
ProVouchers:
load: BEFORE
required: true # or false for a soft integrationThe VoucherService
Obtain it from Bukkit's services manager once ProVouchers has enabled:
import so.alaz.provouchers.api.VoucherService;
VoucherService vouchers = getServer().getServicesManager().load(VoucherService.class);
if (vouchers != null) {
vouchers.getVoucher("crate_key").ifPresent(v ->
getLogger().info("Cooldown: " + v.cooldownSeconds() + "s"));
vouchers.give(player, "crate_key", 1); // async, Folia-safe
}| Method | Returns |
|---|---|
getVoucher(id) | Optional<Voucher> |
voucherIds() | List<String> |
getCode(input) | Optional<VoucherCode> |
voucherCount() / codeCount() | int |
give(player, voucherId, amount) | boolean (false if the id is unknown) |
stash(playerUuid, voucherId, amount) | boolean (queue a virtual voucher in the player's Stash; works offline) |
Voucher and VoucherCode are read-only views (id, display name, lore, flags,
cooldown, expiry, use limits). They are implemented by ProVouchers and must not be
implemented by consumers.
Events
All events live in so.alaz.provouchers.api.event and are standard Bukkit events,
so you listen for them the usual way.
Veto a redemption
VoucherPreRedeemEvent and VoucherCodePreRedeemEvent are cancellable and fire
before the redemption commits, after ProVouchers' own checks. Cancel one to veto:
@EventHandler
public void onPreRedeem(VoucherPreRedeemEvent event) {
if (isFrozen(event.getPlayer())) {
event.setCancelled(true); // anti-cheat, spending limits, etc.
}
}Observe a redemption
VoucherRedeemEvent and VoucherCodeRedeemEvent fire after a successful redeem
(item consumed, anti-dupe passed, rewards granted). They are not cancellable:
@EventHandler
public void onRedeem(VoucherRedeemEvent event) {
getLogger().info(event.getPlayer().getName()
+ " redeemed " + event.getVoucher().id()
+ " (uid " + event.getUid() + ")");
}getUid() is the item's anti-dupe unique id. It is null for a stackable voucher,
which is not dupe-tracked.
| Event | Cancellable | When |
|---|---|---|
VoucherPreRedeemEvent | yes | before an item voucher is redeemed |
VoucherRedeemEvent | no | after an item voucher is redeemed |
VoucherCodePreRedeemEvent | yes | before a code is redeemed |
VoucherCodeRedeemEvent | no | after a code is redeemed |
These events fire on the redeeming player's region thread, not necessarily the global main thread. Keep handlers Folia-safe: schedule any cross-region work through your own scheduler rather than assuming the main thread.
A complete consumer plugin
A small but complete example that ties the pieces together: it resolves the service on enable, gives a welcome voucher when a player first joins, and rewards a token of its own whenever any voucher is redeemed.
package com.example.voucherrewards;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import so.alaz.provouchers.api.VoucherService;
import so.alaz.provouchers.api.event.VoucherRedeemEvent;
public final class VoucherRewardsPlugin extends JavaPlugin implements Listener {
private VoucherService vouchers;
@Override
public void onEnable() {
// ProVouchers registers the service on enable; load BEFORE in paper-plugin.yml
// guarantees it is present by the time we run.
vouchers = getServer().getServicesManager().load(VoucherService.class);
if (vouchers == null) {
getLogger().warning("ProVouchers not found; voucher features disabled.");
return;
}
getLogger().info("Hooked ProVouchers: " + vouchers.voucherCount() + " vouchers loaded.");
getServer().getPluginManager().registerEvents(this, this);
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
if (vouchers != null && !player.hasPlayedBefore()) {
// give(...) is async and Folia-safe; it returns false if the id is not loaded.
vouchers.give(player, "welcome_crate", 1);
}
}
@EventHandler
public void onRedeem(VoucherRedeemEvent event) {
// Fires on the player's region thread, after the redeem has committed.
getLogger().info(event.getPlayer().getName()
+ " redeemed " + event.getVoucher().id());
// ... award loyalty points, update a scoreboard, etc.
}
}name: VoucherRewards
version: 1.0.0
main: com.example.voucherrewards.VoucherRewardsPlugin
api-version: "1.21"
dependencies:
server:
ProVouchers:
load: BEFORE
required: false # soft integration: we degrade gracefully when it is absentWith required: false, guard every use with the vouchers != null check shown above
so your plugin still loads when ProVouchers is not installed. Use required: true if
ProVouchers is mandatory and you would rather fail fast.
Stability
The API is annotated with @ApiStatus.AvailableSince("0.4.0"), and the read-only
types are @ApiStatus.NonExtendable. As of the 1.0.0 release the API surface is
stable; changes follow semantic versioning.