LogoyStore

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 servidor

No 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étodoUso
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.BukkitPlugin

2. 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ê?

  1. O yPlugins ainda está finalizando o bootstrap no mesmo tick do onEnable.
  2. Dependências / soft-depends podem não estar 100% prontas no tick 0.
  3. Evita race com autenticação, injeção de classloader e setup interno.
  4. 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

FaseTickO que fazer
onEnable0Check de classloader, saveDefaultConfig, configs, singleton
preload0YAML auxiliares, comandos se precisar cedo
register1Auth, 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 com if (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
  - PlaceholderAPI

O 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)

  1. extends JavaPlugin → extends BukkitPlugin
  2. Import: br.com.ystoreplugins.platform.BukkitPlugin
  3. Trocar todas as passagens de this / plugin em APIs Bukkit por getLoader()
  4. registerEvents(..., plugin) → registerEvents(..., plugin.getLoader())
  5. Scheduler / BukkitRunnable → usar getLoader()
  6. Registro dinâmico de comando → PluginCommand com owner = getLoader()
  7. Mover init pesado para runTaskLater(getLoader(), this::register, 1)
  8. Adicionar check de classloader no onEnable
  9. Flag disabled para onDisable seguro
  10. Preferir utils do yPlugins (ConfigUtils, etc.) em vez de reinventar
  11. Compilar contra o jar do yPlugins
  12. 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); // Component

HexColor

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) → TextComponent
  • applyLegacy(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);

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, SQLLiteDataSource
  • CachedDataManager, Keyable
  • Utils (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

SintomaCausa provávelCorreção
IllegalPluginAccessException / task não agendaPassou Main em vez do loaderUse getLoader()
Listener não dispararegisterEvents com plugin erradoregisterEvents(l, getLoader())
Comando some / não registraOwner do PluginCommand erradoOwner = getLoader()
NPE no enableDB/hooks no tick 0runTaskLater(..., 1)
"não foi autenticado"Jar rodando fora do yPluginsCarregar via loader autenticado
Config de outro pluginConfundiu data folderUse getDataFolder() do BukkitPlugin

10. Resumo

  1. Main extends BukkitPlugin, não JavaPlugin.
  2. Tudo que pede Plugin usa getLoader() (events, scheduler, commands).
  3. register pesado 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.

Nesta página