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.ymlbuild.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: 30Clase 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 1en logros, niveles, lucky blocks y cualquier recompensa de PulsePrison. - Ver el bonus de racha dentro de
sellen/stats. - Cambiar las recompensas de voto en
config.ymldel addon con las mismas acciones que ya conoce.
Buenas prácticas
- Dependencia:
depend: [PulsePrison]y comprobágetApiVersion()enonEnable. - Módulos: revisá
isModuleEnabledantes 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().runTaskantes 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
| Addon | Con qué |
|---|---|
| Recompensas de votos, crates o battle pass | Acciones propias y actions().run |
| Jefes de mina con recompensas | PrivateMineBlocksMinedEvent, mines().isInAnyMine, acciones |
| Moneda de otro plugin | registerProviderType |
| Encantamientos con lógica compleja | pickaxe().registerEnchant |
| Ranking web o bot de Discord | Servicios de lectura y eventos de nivel, rebirth y mercado |
| Eventos de servidor (hora dorada) | multipliers().setGlobal o /booster global |
| Gremios o clanes externos | clans() y el placeholder de valor de clan |
| Bonus por rango de otro plugin | Fuente de estadísticas |
Ver también Límites actuales.