Docs

Crear un addon

Un addon es un plugin de Paper que depende de PulsePrison y usa su API. Esta página arma uno completo: VoteRewards, que da recompensas configurables por votar, agrega la acción [votekey], suma un bonus de venta por racha de votos y avisa cuando alguien sube de nivel.


Estructura

text
vote-rewards/
├── build.gradle.kts
├── libs/
│   └── PulsePrison-2.0.0.jar
└── src/main/
    ├── java/com/example/voterewards/
    │   ├── VoteRewards.java
    │   ├── StreakStore.java
    │   └── LevelListener.java
    └── resources/
        ├── plugin.yml
        └── config.yml

build.gradle.kts

kotlin
plugins {
    java
}

group = "com.example"
version = "1.0.0"

repositories {
    mavenCentral()
    maven("https://repo.papermc.io/repository/maven-public/")
}

dependencies {
    compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT")
    compileOnly(files("libs/PulsePrison-2.0.0.jar"))
}

java {
    toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}

tasks.processResources {
    filesMatching("plugin.yml") {
        expand("version" to project.version)
    }
}

plugin.yml

yaml
name: VoteRewards
version: '${version}'
main: com.example.voterewards.VoteRewards
api-version: '1.20'
depend: [PulsePrison]
commands:
  vote-reward:
    permission: voterewards.admin
    usage: /<command> <player>

config.yml

yaml
# Actions run for every vote. Any PulsePrison action works here.
rewards:
  - "[give] tokens 2500"
  - "[votekey] common 1"
  - "[chance=5] [votekey] rare 1"
  - "[message] &aThanks for voting!"

# Sell bonus per vote in the current streak, 0.01 is +1%.
streak-sell-bonus: 0.01
max-streak: 30

Clase principal

java
package com.example.voterewards;

import dev.aeros.pulseprison.PulsePrison;
import dev.aeros.pulseprison.api.PulsePrisonAPI;
import dev.aeros.pulseprison.api.PulsePrisonProvider;
import dev.aeros.pulseprison.bonus.BonusService;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.Map;

public final class VoteRewards extends JavaPlugin {

    private PulsePrisonAPI prison;
    private StreakStore streaks;
    private BonusService.BonusProvider streakBonus;

    @Override
    public void onEnable() {
        if (!PulsePrisonProvider.isAvailable() || PulsePrisonProvider.get().getApiVersion() < 3) {
            getLogger().severe("PulsePrison 2.0 or newer is required");
            getServer().getPluginManager().disablePlugin(this);
            return;
        }
        saveDefaultConfig();
        prison = PulsePrisonProvider.get();
        streaks = new StreakStore(this);

        prison.actions().register("votekey", (player, argument) -> {
            String[] parts = argument.split(" ");
            String crate = parts[0];
            String amount = parts.length > 1 ? parts[1] : "1";
            Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "crates key give " + player.getName() + " " + crate + " " + amount);
        });

        double perVote = getConfig().getDouble("streak-sell-bonus");
        streakBonus = (player, sink) -> sink.add("sell", streaks.get(player.getUniqueId()) * perVote);
        PulsePrison.getInstance().getBonusService().register(streakBonus);

        getServer().getPluginManager().registerEvents(new LevelListener(), this);
    }

    @Override
    public void onDisable() {
        if (streakBonus != null && PulsePrison.getInstance() != null) {
            PulsePrison.getInstance().getBonusService().unregister(streakBonus);
        }
        if (streaks != null) {
            streaks.save();
        }
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        Player target = args.length > 0 ? Bukkit.getPlayerExact(args[0]) : null;
        if (target == null) {
            return false;
        }
        int streak = streaks.increment(target.getUniqueId(), getConfig().getInt("max-streak"));
        PulsePrison.getInstance().getBonusService().invalidate(target.getUniqueId());
        prison.actions().run(target, getConfig().getStringList("rewards"), Map.of("streak", String.valueOf(streak)));
        return true;
    }
}

Tu plugin de votos ejecuta vote-reward {player} por consola en cada voto.

Guardar la racha

java
package com.example.voterewards;

import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public final class StreakStore {

    private final File file;
    private final Map<UUID, Integer> streaks = new ConcurrentHashMap<>();

    public StreakStore(JavaPlugin plugin) {
        file = new File(plugin.getDataFolder(), "streaks.yml");
        YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
        for (String key : yaml.getKeys(false)) {
            streaks.put(UUID.fromString(key), yaml.getInt(key));
        }
    }

    public int get(UUID player) {
        return streaks.getOrDefault(player, 0);
    }

    public int increment(UUID player, int max) {
        return streaks.merge(player, 1, (current, one) -> Math.min(max, current + one));
    }

    public void save() {
        YamlConfiguration yaml = new YamlConfiguration();
        streaks.forEach((player, streak) -> yaml.set(player.toString(), streak));
        try {
            yaml.save(file);
        } catch (IOException exception) {
            throw new IllegalStateException("Could not save streaks.yml", exception);
        }
    }
}

get se llama cada vez que PulsePrison recalcula las estadísticas, por eso lee de memoria y no del disco.

Reaccionar a eventos

java
package com.example.voterewards;

import dev.aeros.pulseprison.api.events.PlayerLevelUpEvent;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public final class LevelListener implements Listener {

    @EventHandler
    public void onLevelUp(PlayerLevelUpEvent event) {
        if (event.getTrack().equals("player") && event.getLevel() % 50 == 0) {
            Bukkit.broadcastMessage(event.getPlayer().getName() + " reached player level " + event.getLevel()
                    + ". Vote to reach the next one faster!");
        }
    }
}

Resultado para el dueño del servidor

Sin tocar Java, el dueño del servidor ahora puede:

  • Usar [votekey] legendary 1 en logros, niveles, lucky blocks y cualquier recompensa de PulsePrison.
  • Ver el bonus de racha dentro de sell en /stats.
  • Cambiar las recompensas de voto en config.yml del addon con las mismas acciones que ya conoce.

Buenas prácticas

  • Dependencia: depend: [PulsePrison] y comprobá getApiVersion() en onEnable.
  • Módulos: revisá isModuleEnabled antes de ofrecer algo que depende de un sistema opcional.
  • Hilo principal: llamá a la API desde el hilo principal. Si hacés trabajo pesado en otro hilo, volvé con Bukkit.getScheduler().runTask antes de tocar PulsePrison.
  • Limpieza: en onDisable, quitá encantamientos y fuentes de estadísticas que registraste.
  • Configurable: exponé tus valores en YAML y aceptá acciones de PulsePrison en las recompensas, así el dueño del servidor no aprende otro formato.
  • Monedas por id: pedí el id de la moneda en tu configuración en vez de fijar tokens: cada servidor tiene las suyas.
  • Idiomas: si tu addon envía textos, usá tu propio archivo de mensajes. Las claves @... de PulsePrison solo resuelven textos de sus archivos.
  • API pública primero: preferí dev.aeros.pulseprison.api. Lo que uses fuera de ese paquete (BaseEnchant, BonusService) puede cambiar con una versión mayor; aislalo en una clase para adaptarlo fácil.

Ideas de addons

AddonCon qué
Recompensas de votos, crates o battle passAcciones propias y actions().run
Jefes de mina con recompensasPrivateMineBlocksMinedEvent, mines().isInAnyMine, acciones
Moneda de otro pluginregisterProviderType
Encantamientos con lógica complejapickaxe().registerEnchant
Ranking web o bot de DiscordServicios de lectura y eventos de nivel, rebirth y mercado
Eventos de servidor (hora dorada)multipliers().setGlobal o /booster global
Gremios o clanes externosclans() y el placeholder de valor de clan
Bonus por rango de otro pluginFuente de estadísticas

Ver también Límites actuales.

Building an addon | PulsePrison Core Docs