Desenvolva com o yPlugins
Guia prático para converter ou criar um plugin Spigot/Paper compatível com o loader do yPlugins.
Guia prático para converter (ou criar) um plugin Spigot/Paper que rode dentro do yPlugins, e não como JavaPlugin standalone.
Visão geral
No modelo normal do Bukkit:
JavaPlugin (seu plugin) → é o Plugin registrado no servidorNo yPlugins:
BukkitBootstrap (yPlugins) → JavaPlugin real / dono de tasks, listeners, commands
└── carrega sua Main (extends BukkitPlugin)Ou seja: o plugin registrado no servidor é o loader (BukkitBootstrap). A sua classe principal não estende JavaPlugin — estende BukkitPlugin e usa getLoader() sempre que a API do Bukkit exige um Plugin / JavaPlugin.
Este guia cobre a adaptação do plugin para o loader. Para consumir a API pública dos plugins yStore, veja também a API dos Plugins.
1. Classe principal: BukkitPlugin
Antes (standalone)
public final class Main extends JavaPlugin {
@Override
public void onEnable() {
// ...
}
}Depois (yPlugins)
import br.com.ystoreplugins.platform.BukkitPlugin;
public final class Main extends BukkitPlugin {
private static Main instance;
@Override
public void onEnable() {
// verificação de loader + bootstrap
}
@Override
public void onDisable() {
// cleanup
}
public static Main get() {
return instance;
}
}O que BukkitPlugin oferece
API parecida com a do JavaPlugin, mas delegada ao loader:
| Método | Uso |
|---|---|
getLoader() | Retorna o BukkitBootstrap (o JavaPlugin real) |
getDataFolder() | Pasta de dados do produto |
getConfig() / saveConfig() / reloadConfig() / saveDefaultConfig() | config.yml |
saveResource(name, replace) | Extrair resource do jar |
getResource(name) | Stream de resource |
getLogger() | Logger |
getServer() | Bukkit.getServer() |
getName() / getVersion() | Metadados |
getDescription() | PluginDescriptionFile |
isEnabled() | Estado |
BukkitPlugin.getPlugin(Class) | Acesso estático genérico |
Pacote:
br.com.ystoreplugins.platform.BukkitPlugin2. Regra de ouro: sempre use getLoader()
Qualquer API do Bukkit que peça Plugin não pode receber a sua Main (ela não é Plugin). Use o loader:
// Listeners
Bukkit.getPluginManager().registerEvents(listener, main.getLoader());
// Scheduler
Bukkit.getScheduler().runTask(main.getLoader(), () -> { ... });
Bukkit.getScheduler().runTaskLater(main.getLoader(), () -> { ... }, 20L);
Bukkit.getScheduler().runTaskAsynchronously(main.getLoader(), () -> { ... });
Bukkit.getScheduler().runTaskTimer(main.getLoader(), runnable, delay, period);
Bukkit.getScheduler().runTaskTimerAsynchronously(main.getLoader(), runnable, delay, period);
// BukkitRunnable
new BukkitRunnable() {
@Override
public void run() { ... }
}.runTaskTimer(main.getLoader(), 0L, 20L);
// PluginCommand (reflexão / registro dinâmico)
PluginCommand cmd = constructor.newInstance(name, Main.get().getLoader());Errado
// NÃO faça isso — Main não é JavaPlugin/Plugin
Bukkit.getPluginManager().registerEvents(this, main);
Bukkit.getScheduler().runTask(main, () -> {});Certo
Bukkit.getPluginManager().registerEvents(this, main.getLoader());
Bukkit.getScheduler().runTask(main.getLoader(), () -> {});O que é o loader?
br.com.ystoreplugins.bootstrap.BukkitBootstrap extends JavaPluginÉ o bootstrap do yPlugins no servidor. getLoader() devolve essa instância.
3. register 1 tick depois
Não registre listeners, tasks, database e hooks pesados direto no onEnable.
Adie o register em 1 tick.
Por quê?
- O yPlugins ainda está finalizando o bootstrap no mesmo tick do
onEnable. - Dependências / soft-depends podem não estar 100% prontas no tick 0.
- Evita race com autenticação, injeção de classloader e setup interno.
- Padrão usado nos produtos yStore.
Padrão recomendado
@Override
public void onEnable() {
// 1) Garante que o classloader é o do yPlugins
if (!getClass().getClassLoader().getClass().getCanonicalName().startsWith("br.com.ystoreplugins")) {
getLogger().severe("Este plugin não foi autenticado no yPlugins.");
disabled = true;
return;
}
getLogger().info("Plugin carregado.");
instance = this;
// 2) Coisas leves e síncronas (configs, comandos leves)
saveDefaultConfig();
preload();
// 3) Register pesado no próximo tick
Bukkit.getScheduler().runTaskLater(this.getLoader(), this::register, 1);
}
private void preload() {
// Configs, registro de comando (se necessário cedo)
loadConfigs();
new MeuCommand(this);
}
private void register() {
// Auth, DB, hooks, listeners, tasks, managers
if (!CoreAuth.verify()) return;
if (!DatabaseEngine.init(this)) return;
hooks();
common();
new PlayerListener(this);
new MinhaTask(this);
}
@Override
public void onDisable() {
if (disabled) return;
// salvar dados, cancelar sessões, etc.
}Separação mental
| Fase | Tick | O que fazer |
|---|---|---|
onEnable | 0 | Check de classloader, saveDefaultConfig, configs, singleton |
preload | 0 | YAML auxiliares, comandos se precisar cedo |
register | 1 | Auth, DB, listeners, tasks, menus, hooks (PAPI, etc.) |
onDisable | — | Save / cleanup (respeitando flag disabled) |
4. Check de autenticação / classloader
O jar não deve rodar solto em plugins/. Ele precisa ser carregado pelo yPlugins.
if (!getClass().getClassLoader().getClass().getCanonicalName().startsWith("br.com.ystoreplugins")) {
getLogger().severe("Este plugin não foi autenticado no yPlugins.");
disabled = true;
return;
}Se falhar:
- não chame
register - no
onDisable, saia cedo comif (disabled) return;
Opcionalmente (produtos oficiais) há também verificação de core (CoreAuth / PluginCore / Generator) — isso é específico do pipeline yStore e não é obrigatório para todo adapter genérico.
5. plugin.yml e dependência no build
plugin.yml
O main continua apontando para a sua classe (a que estende BukkitPlugin). O yPlugins usa isso ao instanciar o produto.
name: MeuPlugin
version: ${project.version}
main: com.exemplo.meu.Main
api-version: "1.13"
authors: [yStore]
softdepend:
- yCore
- PlaceholderAPIO servidor em si carrega o yPlugins (BukkitBootstrap). Seu jar entra pelo loader, não precisa estar como plugin solto se o fluxo for 100% via yPlugins.
Dependência no build
Compile com Spigot/Paper + yPlugins no classpath. Não shade o yPlugins dentro do seu jar (ele já existe no servidor).
Use o repositório oficial e a dependência provided / compileOnly. Veja os exemplos em API dos Plugins.
<repositories>
<repository>
<id>ystoreplugins</id>
<url>https://repo.ystoreplugins.com.br/releases/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.ystoreplugins</groupId>
<artifactId>yplugins</artifactId>
<version>VERSION</version>
<scope>provided</scope>
</dependency>
</dependencies>6. Checklist de migração (JavaPlugin → BukkitPlugin)
-
extends JavaPlugin→extends BukkitPlugin - Import:
br.com.ystoreplugins.platform.BukkitPlugin - Trocar todas as passagens de
this/pluginem APIs Bukkit porgetLoader() -
registerEvents(..., plugin)→registerEvents(..., plugin.getLoader()) - Scheduler /
BukkitRunnable→ usargetLoader() - Registro dinâmico de comando →
PluginCommandcom owner =getLoader() - Mover init pesado para
runTaskLater(getLoader(), this::register, 1) - Adicionar check de classloader no
onEnable - Flag
disabledparaonDisableseguro - Preferir utils do yPlugins (
ConfigUtils, etc.) em vez de reinventar - Compilar contra o jar do yPlugins
- Testar no servidor com yPlugins autenticando o produto
7. Utils disponíveis no yPlugins
Pacote principal: com.ystoreplugins.util.*
(e alguns em com.ystoreplugins.menu, com.ystoreplugins.ymodel.util)
Configuração
ConfigUtils
Arquivos YAML auxiliares (além do config.yml).
import com.ystoreplugins.util.ConfigUtils;
// Aceita BukkitPlugin ou JavaPlugin
ConfigUtils messages = new ConfigUtils(this, "messages.yml");
FileConfiguration cfg = messages.getConfig();
messages.save();
File file = messages.getFile();Construtores:
ConfigUtils(JavaPlugin, String)ConfigUtils(BukkitPlugin, String)
Mensagens e cores
MessageUtil
Envio de mensagens com suporte a cores / MiniMessage / Adventure.
import com.ystoreplugins.util.MessageUtil;
MessageUtil.send(sender, "&aOlá!");
MessageUtil.send(player, mensagem);
MessageUtil.sendTitle(player, "Titulo\nSubtitulo");
MessageUtil.sendActionbar(player, "&eActionbar");
MessageUtil.sendJson(player, json);
MessageUtil.colorize(raw); // ComponentHexColor
Converte hex / formatação de cor para string colorida.
import com.ystoreplugins.util.HexColor;
String colored = HexColor.hex("&aTexto #FF00AA");ColorUtil
API mais completa com Adventure:
apply(String)→TextComponentapplyLegacy(String)apply(List<String>)legacy(String)/colorizeToLegacy(String)noColor(Component)
Itens
ItemBuilder
Builder fluente de ItemStack.
import com.ystoreplugins.util.item.ItemBuilder;
ItemStack item = new ItemBuilder(Material.DIAMOND_SWORD)
.setName("&bEspada")
.setLore("&7Linha 1", "&7Linha 2")
.setGlow(true)
.setAmount(1)
.toItemStack();Principais métodos: setName, setLore, addLoreLine, setAmount, setDurability, addEnchant, setGlow, setSkullOwner, setCustomData, setLeatherArmorColor, addItemFlag, etc.
SetupItem
Monta item a partir de ConfigurationSection (padrão dos menus yStore).
import com.ystoreplugins.util.item.SetupItem;
ItemStack item = SetupItem.setup(config.getConfigurationSection("menu.item"), true);
// ou
ItemStack item2 = new SetupItem().setupMenuItem(section, true);NBTTag
NBT multi-versão (1.8+).
import com.ystoreplugins.util.item.NBTTag;
NBTTag.setNBTString(item, "chave", "valor");
String v = NBTTag.getNBTString(item, "chave");
NBTTag.setNBTInteger(item, "id", 10);
// também: setNBTByte, setNBTShort, getAllNBTString
// helpers de versão: isOldest(), isNewest(), isSuperNewest(), isVeryNew()CustomSkull
Cabeças custom (texture / owner).
import com.ystoreplugins.util.item.CustomSkull;
ItemStack skull = new CustomSkull().getSkull(ownerOrTexture, name, lore, glow);Formatação e validação
Formatter
Números, tempo, sim/não, on/off.
import com.ystoreplugins.util.Formatter;
Formatter.init(plugin); // se necessário
String n = Formatter.letterFormatter(1500); // ex: 1.5k
String d = Formatter.doubleFormatter(10.5);
String t = Formatter.getTime(millis);
boolean ok = Formatter.deform("1.5k") > 0;
String s = Formatter.simOuNao(true, true);Progressbar
Barra de progresso em string.
import com.ystoreplugins.util.Progressbar;
String bar = Progressbar.getProgressBar(current, max);
String bar2 = Progressbar.getProgressBar(current, max, size, symbol, completed, notCompleted);
double pct = Progressbar.getPercentage(current, max);Verify
Helpers diversos:
isItemNull(ItemStack)isNumeric(String)getChance(double)find(String material, String fallback)getHora()getTarget(Player)getStringList(String)(split utilitário)
CommandUtils
import com.ystoreplugins.util.CommandUtils;
Optional<Player> player = CommandUtils.requirePlayer(sender);
boolean ok = CommandUtils.checkPermission(sender, "meu.perm");Localização e inventário
LocationUtils
import com.ystoreplugins.util.LocationUtils;
String s = LocationUtils.serialize(location);
Location loc = LocationUtils.deserialize(s);InventoryUtil
import com.ystoreplugins.util.InventoryUtil;
Inventory top = InventoryUtil.getTopInventory(event);
String title = InventoryUtil.getTitle(event);
Inventory top2 = InventoryUtil.getTopInventory(player);Menus
MenuUtils (com.ystoreplugins.menu.MenuUtils)
Paginação, slots, botões de página, separadores.
import com.ystoreplugins.menu.MenuUtils;
int pages = MenuUtils.calculateTotalPages(totalItems);
boolean need = MenuUtils.needsPagination(totalItems);
int size = MenuUtils.calculateInventorySize(items);
MenuUtils.addPaginationRow(inv, title, page, totalPages, size);
ItemStack next = MenuUtils.createNextPageButton(name, page, total);
ItemStack prev = MenuUtils.createPreviousPageButton(name, page, total);Scheduler multiplataforma (Bukkit / Folia)
import com.ystoreplugins.util.scheduler.SchedulerProvider;
import com.ystoreplugins.util.scheduler.TaskScheduler;
TaskScheduler sch = SchedulerProvider.get();
boolean folia = SchedulerProvider.isFolia();
sch.runTask(main.getLoader(), () -> { ... });
sch.runTaskLater(main.getLoader(), () -> { ... }, 20L);
sch.runAsync(main.getLoader(), () -> { ... });
sch.runAtLocation(main.getLoader(), loc, () -> { ... });
sch.runForEntity(main.getLoader(), entity, () -> { ... });Mesmo com SchedulerProvider, o Plugin passado deve ser getLoader().
Registro de comando (API do yPlugins)
Além do util local do projeto, o yPlugins expõe:
import com.ystoreplugins.ymodel.util.RegisterCommand;
new RegisterCommand(name, executor, aliases, main.getLoader());Se implementar o seu próprio, o owner do PluginCommand tem que ser getLoader():
PluginCommand cmd = constructor.newInstance(command, Main.get().getLoader());Database (lib embutida)
Pacote: br.com.ystoreplugins.lib.databasemodel.*
AbstractDataSource,HikariDataSource,MySQLDataSource,SQLLiteDataSourceCachedDataManager,KeyableUtils(log / helpers)- Redis via
br.com.ystoreplugins.lib.jedis.*
Útil se o produto seguir o padrão de storage yStore.
8. Exemplo mínimo completo
package com.exemplo.demo;
import br.com.ystoreplugins.platform.BukkitPlugin;
import com.ystoreplugins.util.ConfigUtils;
import com.ystoreplugins.util.MessageUtil;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
public final class Main extends BukkitPlugin implements Listener {
private static Main instance;
private ConfigUtils messages;
private boolean disabled;
@Override
public void onEnable() {
if (!getClass().getClassLoader().getClass().getCanonicalName()
.startsWith("br.com.ystoreplugins")) {
getLogger().severe("Este plugin não foi autenticado no yPlugins.");
disabled = true;
return;
}
instance = this;
saveDefaultConfig();
messages = new ConfigUtils(this, "messages.yml");
Bukkit.getScheduler().runTaskLater(getLoader(), this::register, 1);
}
private void register() {
Bukkit.getPluginManager().registerEvents(this, getLoader());
getLogger().info("Sistemas registrados.");
}
@EventHandler
public void onJoin(PlayerJoinEvent e) {
MessageUtil.send(e.getPlayer(), messages.getConfig().getString("join", "&aBem-vindo!"));
}
@Override
public void onDisable() {
if (disabled) return;
getLogger().info("Plugin descarregado.");
}
public static Main get() {
return instance;
}
}9. Erros comuns
| Sintoma | Causa provável | Correção |
|---|---|---|
IllegalPluginAccessException / task não agenda | Passou Main em vez do loader | Use getLoader() |
| Listener não dispara | registerEvents com plugin errado | registerEvents(l, getLoader()) |
| Comando some / não registra | Owner do PluginCommand errado | Owner = getLoader() |
| NPE no enable | DB/hooks no tick 0 | runTaskLater(..., 1) |
| "não foi autenticado" | Jar rodando fora do yPlugins | Carregar via loader autenticado |
| Config de outro plugin | Confundiu data folder | Use getDataFolder() do BukkitPlugin |
10. Resumo
Main extends BukkitPlugin, nãoJavaPlugin.- Tudo que pede
PluginusagetLoader()(events, scheduler, commands). registerpesado só 1 tick depois (runTaskLater(getLoader(), this::register, 1)).
Aproveite as utils em com.ystoreplugins.util (ConfigUtils, MessageUtil, HexColor, ItemBuilder, SetupItem, NBTTag, Formatter, etc.) para manter o código no padrão yStore.