Plataformas
O mesmo .jar roda nas seis. A detecção é por reflexão, sem compilar contra API nenhuma.
| Plataforma | Thread principal | Como o SDK despacha |
|---|---|---|
| Paper | sim | Bukkit.getScheduler().runTask |
| Spigot | sim | Bukkit.getScheduler().runTask |
| Purpur | sim | igual ao Paper (detectado como Paper) |
| Folia | não existe | GlobalRegionScheduler.execute |
| Velocity | não existe | executa direto — não há para onde voltar |
| BungeeCord | sim | ProxyServer.getScheduler().runAsync |
Por que reflexão
A alternativa seria compilar contra paper-api, velocity-api e bungeecord-api ao mesmo tempo — e um plugin de Velocity carregaria classes do Bukkit que não existem lá, falhando com NoClassDefFoundError no primeiro uso.
Com reflexão, o mesmo artefato roda em todas, sem recompilar e sem um sub-artefato por plataforma. O custo é uma busca de classe no boot, feita uma vez e guardada.
Folia é um caso à parte
No Folia não existe “a thread principal”
Bukkit.getScheduler().runTask(...) lá lança UnsupportedOperationException em boa parte dos casos.O equivalente correto é o GlobalRegionScheduler, usado para o que não pertence a nenhuma região — e o estado de licença é exatamente isso.
Por isso a detecção testa Folia antes de Bukkit: o Folia também tem as classes do Bukkit, e checar na ordem errada escolheria o agendador que não funciona.
Declarar suporte a Folia
name: SeuPlugin
version: '1.0.0'
main: com.seupacote.SeuPlugin
api-version: '1.21'
# Só declare isto se o seu plugin REALMENTE respeitar os agendadores por
# região. Declarar sem respeitar produz corrupção silenciosa de estado.
folia-supported: trueUso
// Uma vez, no onEnable:
var mainThread = PlatformSupport.dispatcher(this);
// Em qualquer callback do SDK:
mainThread.run(() -> {
// Aqui a API do servidor pode ser tocada com segurança,
// em qualquer uma das seis plataformas.
Bukkit.broadcastMessage("Licença autorizada.");
});No Velocity, passe o ProxyServer em vez do plugin — é ele que tem o agendador. Quando a plataforma não é reconhecida, o despachante executa na thread chamadora: correto para teste e ferramenta de linha de comando, e seguro, porque nada é silenciosamente descartado.
Velocity e BungeeCord
O que muda em um proxy:
- Não há thread de tick no Velocity. Ele é inteiramente assíncrono; o despachante executa direto.
- O logger é SLF4J, injetado. Não há
getLogger()herdado. - A pasta de dados vem por injeção (
@DataDirectory) e pode não existir ainda — crie antes de entregar ao SDK.
@Plugin(id = "seu-plugin", name = "Seu Plugin", version = "1.0.0")
public final class SeuPluginVelocity {
private final ProxyServer proxy;
private final Logger logger;
private final Path dataDirectory;
private LodestoneLicensing licensing;
@Inject
public SeuPluginVelocity(ProxyServer proxy, Logger logger, @DataDirectory Path dataDirectory) {
this.proxy = proxy;
this.logger = logger;
this.dataDirectory = dataDirectory;
}
@Subscribe
public void onProxyInitialize(ProxyInitializeEvent event) {
Files.createDirectories(dataDirectory);
licensing = LodestoneLicensing.builder()
.productId(PRODUCT_ID)
.licenseKey(lerChave())
.dataFolder(dataDirectory)
.platform(PlatformSupport.detect().id())
.serverVersion(proxy.getVersion().getVersion())
.build();
licensing.activateAsync();
}
@Subscribe
public void onProxyShutdown(ProxyShutdownEvent event) {
if (licensing != null) licensing.close();
}
}Proxy e vínculo por IP não combinam
O que não muda
Tudo que importa: o mesmo productId, a mesma chave, a mesma verificação de assinatura, o mesmo modo offline, o mesmo close(). O SDK não tem variante por plataforma — e não ter é o ponto.