Docs

Extending PulsePrison

Besides reading and changing data, a plugin can add content that server owners then use from YAML: action types, currency providers, enchants and stat sources.

WhatHowStatus
Custom actionsactions().registerPublic API
Currencies from your plugincurrencies().registerProviderTypePublic API
Java enchantspickaxe().registerEnchantPublic API, the base class is internal
Stat sourcesBonusService.registerAdvanced, internal

Custom actions

Register a type and it works in every action list: level rewards, milestones, masteries, boosters, abilities, lucky blocks, AFK blocks, custom enchants and menus.

java
prison.actions().register("crate", (player, argument) -> {
    String[] parts = argument.split(" ");
    crates.giveKey(player, parts[0], parts.length > 1 ? Integer.parseInt(parts[1]) : 1);
});

The server owner uses it like this:

yaml
rewards:
  levels:
    "25":
      - "[crate] legendary 2"
      - "[chance=10] [crate] mythic"
  • argument arrives with {player}, the system placeholders and PlaceholderAPI placeholders already replaced.
  • The [chance], [delay] and [permission] prefixes are resolved by PulsePrison before calling you.
  • The handler runs on the main thread.
  • Register in onEnable. A type with the same name as another replaces it.

Currencies from your plugin

Add a new provider: value for currencies.yml. Any server can then use your currency in costs, rewards, /balance, /pay and placeholders without configuring commands.

java
prison.currencies().registerProviderType("myaddon", (currencyId, section) -> {
    String account = section.getString("account", currencyId);
    return new CurrencyApi.ExternalCurrency() {
        @Override
        public double balance(UUID player) {
            return bank.get(player, account);
        }

        @Override
        public boolean add(UUID player, double amount) {
            return bank.deposit(player, account, amount);
        }

        @Override
        public boolean take(UUID player, double amount) {
            return bank.withdraw(player, account, amount);
        }

        @Override
        public boolean set(UUID player, double amount) {
            return bank.set(player, account, amount);
        }

        @Override
        public boolean available() {
            return bank.isConnected();
        }
    };
});
yaml
# the server's currencies.yml
currencies:
  souls:
    provider: myaddon
    account: souls          # any extra option arrives in "section"
    name: "Souls"
    format: "&5{amount} {name}"
  • When the type is registered, PulsePrison reloads currencies.yml, so currencies that already used it activate even though your plugin loads later.
  • Return null from the factory if the section is misconfigured: that currency isn't loaded and the console reports it.
  • If your source can disconnect, make take return false meanwhile: costs are not charged.

Java enchants

For effects YAML can't express, extend BaseEnchant. The enchant shows in the pickaxe menu, is bought with its currency, respects conflicts and requirements, earns mastery and runs while mining.

BaseEnchant, EnchantContext and EnchantLoader.EnchantParams live outside the api package: they may change in a major version.

Parameters:

java
import dev.aeros.pulseprison.enchants.base.EnchantRarity;
import dev.aeros.pulseprison.enchants.base.EnchantTrigger;
import dev.aeros.pulseprison.enchants.base.EnchantType;
import dev.aeros.pulseprison.enchants.registry.EnchantLoader.EnchantParams;

public final class SoulParams {

    public static EnchantParams create() {
        EnchantParams p = new EnchantParams();
        p.id = "soul_harvest";
        p.displayName = "&5☠ Soul Harvest";
        p.type = EnchantType.PICKAXE;
        p.trigger = EnchantTrigger.POST_BREAK;
        p.rarity = EnchantRarity.EPIC;
        p.maxLevel = 50;
        p.baseCost = 100000;
        p.costMultiplier = 1.08;
        p.currency = "tokens";
        p.requiredPickaxeLevel = 20;
        p.guiSlot = 43;
        p.guiItem = "WITHER_SKELETON_SKULL";
        p.levelValues = Map.of(1, 1.0, 50, 25.0);
        p.levelChances = Map.of(1, 0.5, 50, 5.0);
        p.descriptionEN = List.of("&7Harvest souls while mining.", "&7Level {level}: &f{value} souls ({chance}%)");
        p.descriptionES = List.of("&7Cosecha almas al minar.", "&7Nivel {level}: &f{value} almas ({chance}%)");
        return p;
    }
}

Enchant: BaseEnchant has a protected constructor with the EnchantParams fields in order, the same pattern the built-in enchants use.

java
import dev.aeros.pulseprison.api.PulsePrisonProvider;
import dev.aeros.pulseprison.enchants.base.BaseEnchant;
import dev.aeros.pulseprison.enchants.base.EnchantContext;
import dev.aeros.pulseprison.enchants.registry.EnchantLoader.EnchantParams;
import dev.aeros.pulseprison.enchants.utils.ChanceCalculator;

public final class SoulHarvestEnchant extends BaseEnchant {

    public SoulHarvestEnchant(EnchantParams p) {
        super(p.id, p.displayName, p.type, p.trigger, p.rarity,
              p.maxLevel, p.baseCost, p.costMultiplier, p.currency,
              p.requiredPickaxeLevel, p.conflictsWith, p.requires,
              p.guiSlot, p.guiItem, p.levelValues, p.levelChances,
              p.descriptionES, p.descriptionEN);
    }

    @Override
    public boolean activate(EnchantContext context) {
        int level = context.getEnchantLevel();
        if (!ChanceCalculator.roll(getLevelChance(level))) {
            return false;
        }
        double souls = getLevelValue(level);
        PulsePrisonProvider.get().currencies().give(context.getPlayer().getUniqueId(), "souls", souls);
        return true;
    }
}

Registration:

java
@Override
public void onEnable() {
    prison = PulsePrisonProvider.get();
    prison.pickaxe().registerEnchant(new SoulHarvestEnchant(SoulParams.create()));
}

@Override
public void onDisable() {
    if (PulsePrisonProvider.isAvailable()) {
        PulsePrisonProvider.get().pickaxe().unregisterEnchant("soul_harvest");
    }
}

What to do in activate

  • Return true only when the enchant did something: it counts for masteries and activation messages.
  • Use ChanceCalculator.roll(getLevelChance(level)): it applies the enchant-chance stat and the skill tree.
  • getLevelValue and getLevelChance already interpolate between the levels in levelValues and levelChances.
  • Paying: the enchant pays its own rewards. For tokens and gems, pay the base amount with economy().addTokens or addGems and report it with context.addTokenBonus or addGemBonus: PulsePrison pays the extra from bonuses (skill tree, pet, stats) on top and shows it in the action bar. For money, pay and report with context.addMoneyBonus.
  • Other currencies: currencies().give.
  • Pickaxe experience: context.multiplyExp(1.5).
  • PRE_BREAK runs before the block is paid and PASSIVE on every block without its own chance.

An enchant registered from Java doesn't read enchants.yml: its values are those in EnchantParams. If you want server owners to change them, read them from your plugin's own config file.

Stat sources

Advanced. An addon can add to the stats with the same interface attributes, crystals and armor use. The bonuses show in /stats and affect everything that uses that stat.

java
BonusService.BonusProvider provider = (player, sink) -> {
    int prestige = myRanks.getLevel(player);
    sink.add("sell", prestige * 0.01);
    sink.add("enchant-chance:soul_harvest", prestige * 0.02);
};

PulsePrison.getInstance().getBonusService().register(provider);
  • contribute is called often: PulsePrison caches the total per player for one second. Keep it fast and free of database queries.
  • If your values change suddenly, call getBonusService().invalidate(uuid).
  • Remove it in onDisable with unregister(provider).

dev.aeros.pulseprison.bonus.BonusService is internal: it may change between versions.


Current limits

  • Effect types for custom-enchants/: YAML enchants load when PulsePrison starts, before addons, so an addon can't add new effect types yet. Use custom actions inside the actions effect, or a Java enchant.
  • Menus: an addon can't add actions or placeholders to PulsePrison menus. It can add buttons that run its commands or its custom actions.
  • Events: the newer progression systems have no events. See What has no event.
  • Artifact: there is no Maven repository; compile against the jar.
Extending PulsePrison | PulsePrison Core Docs