Contenido generado con IA — puede contener errores.

Empezarguía

Un primer plugin

Config, un comando, un menú y una tabla — de principio a fin, un archivo cada uno.

Todo lo de abajo es API real. Es el camino más corto desde un plugin vacío hasta uno que guarda algo, muestra un menú y se puede recargar.

La configuración

Un archivo YAML es un record de Java. El record es la fuente de verdad; el archivo se genera a partir de él.

Settings.java
public record Settings(
        @Comment("Cuántos kits puede tener un jugador.")
        @Key("max-kits")
        int maxKits,
 
        @Comment("Se muestra encima de la lista de kits.")
        String header) {
 
    /** Los valores por defecto con los que se genera el archivo. */
    public Settings() {
        this(3, "{primary}&lTUS KITS");
    }
}
settings = Configs.define(this, "config", Settings.class).load();
int max = settings.get().maxKits();

get() es un acceso a campo, nunca un reparseo, así que leer un valor dentro de un bucle no cuesta nada.

La tabla

Otro record, anotado con dónde vive:

Kit.java
@Table("my_kits")
public record Kit(
        @Id(length = 64) String id,
        @Column(length = 36) String owner,
        @Column(length = 128) String name,
        @Column(length = Column.UNBOUNDED) ItemStack[] contents) {
}
Repository<Kit> kits = Databases.of(this).repository(Kit.class);
 
kits.find(id).thenAccept(found ->
        tasks.runAtEntity(player, () -> found.ifPresent(kit -> give(player, kit))));

Cada llamada devuelve un CompletableFuture y ninguna bloquea. Volver a Bukkit significa volver por el scheduler — runAtEntity sobre el jugador, que en Folia es el hilo de su región.

El menú

El menú es un archivo, no código:

menus/kits.yml
title: '{primary}&lKITS {muted}%current_page%/%total_pages%'
size: 54
animation: center_out
 
pagination:
  slots: '10-16,19-25,28-34'
  item_template:
    material: "%kit_icon%"
    name: "{warning}&l%kit_name%"
    lore:
      - "{muted}Clic para equipar"
    actions:
      - "myplugin:equip %kit_id%"
  navigation:
    previous: { slot: 45, material: ARROW, name: "{muted}Atrás" }
    next:     { slot: 53, material: ARROW, name: "{muted}Siguiente" }
 
filler:
  global:
    material: BLACK_STAINED_GLASS_PANE
    hide_tooltip: true

Se carga una vez y se abre barato:

menus.load("kits", YamlConfiguration.loadConfiguration(
        new File(getDataFolder(), "menus/kits.yml")));
 
menus.open(player, "kits");

Llenar la lista es donde tus datos se encuentran con la plantilla:

UiSession session = menus.openNow(player, menus.definition("kits").orElseThrow(), Map.of());
 
session.entries(owned.stream()
        .map(kit -> UiEntry.of(kit)
                .with("kit_name", kit.name())
                .with("kit_icon", kit.icon())
                .with("kit_id", kit.id())
                .build())
        .toList());

La acción detrás del botón

myplugin:equip %kit_id% es una acción. Regístrala una vez y cualquier menú, item o frontera de evento puede llamarla:

Actions.of(this).registerSync("equip", (context, args) -> {
    Player player = context.player();
    Kit kit = (Kit) context.require(UiKeys.ENTRY);   // la fila que se pulsó
    give(player, kit);
    return ActionResult.success();
});

La fila la lleva el clic, así que nada tiene que deducir qué kit se dibujó dónde. Ver Acciones.

Recargar

Declara los pasos una vez y /myplugin reload pasa a ser una llamada que dice qué paso falló en vez de morirse a medias:

Reloads.of(this)
        .step("configs", () -> { settings.reload(); menus.reload(); })
        .step("kits", () -> kitManager.load());
 
// Y cuando cambien los colores de la propia librería:
Reloads.onLibraryReload(this, () -> menus.reload());

Lo que no tuviste que escribir

Ningún listener de inventario, ningún mapa por UUID, ningún parseo de YAML, ningún malabar de hilos alrededor de la base de datos, ningún equipo de scoreboard para un nametag, y ninguna rama para Folia en ninguna parte.

Siguiente: Configuración para el formato del archivo, o Menús para todo lo que puede decir un menú.

¿Falta algo en esta página? Dínoslo en Discord