Быстрый старт
Очень подробный стартовый путь NextLib 1.0.8: от пустого плагина до рабочего ядра с БД, GUI, reload, i18n, метриками и квестами.
Быстрый старт
Этот раздел сделан как «практический первый день»: вы можете пройти его шаг за шагом и получить рабочий каркас плагина на NextLib с понятной архитектурой.
Что мы соберём в конце
После прохождения у вас будет:
- единый
NextLibContext(reload + i18n + metrics + health + message bus); - подключенная БД (
DatabaseManager,DynamicDatabase,DynamicTable); - первое GUI меню из YAML;
- первая команда (
/myplugin reload); - базовый troubleshooting и эксплуатационный чеклист.
Пререквизиты
Нужно заранее:
- Paper/Spigot сервер совместимой версии.
- Java и Gradle/Maven в рабочем состоянии.
- Понимание базового
JavaPluginlifecycle (onEnable/onDisable).
Шаг 1. Подключите NextLib и драйвер БД
Gradle (Kotlin DSL)
repositories {
maven("https://jitpack.io")
}
dependencies {
implementation("com.github.chi2l3s:next-lib:1.0.8")
implementation("org.xerial:sqlite-jdbc:3.46.0.0")
// либо MySQL/PostgreSQL драйвер
}
Maven
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.chi2l3s</groupId>
<artifactId>next-lib</artifactId>
<version>1.0.8</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.46.0.0</version>
</dependency>
</dependencies>
Важно: если JDBC-драйвер не попадёт в runtime, БД не инициализируется.
Шаг 2. Создайте базовый класс плагина
public final class MyPlugin extends JavaPlugin {
private NextLibContext context;
private DatabaseManager databaseManager;
private DynamicDatabase dynamicDatabase;
private DynamicTable<PlayerEntity> players;
private GuiManager guiManager;
@Override
public void onEnable() {
bootstrapContext();
bootstrapDatabase();
bootstrapGui();
bootstrapReloads();
bootstrapHealthAndMetrics();
}
@Override
public void onDisable() {
shutdownDatabase();
}
private void bootstrapContext() {
context = NextLibContext.bootstrap(this);
}
private void bootstrapDatabase() {
databaseManager = new DatabaseManager();
DatabaseConfig config = DatabaseConfig.builder(DatabaseType.SQLITE)
.file(getDataFolder() + "/database.db")
.property("maximumPoolSize", "5")
.property("connectionTimeout", "30000")
.build();
DatabaseClient client = databaseManager.register("main", config);
dynamicDatabase = new DynamicDatabase(client)
.withAutoMigrations(this);
players = dynamicDatabase.register("players", PlayerEntity.class);
}
private void bootstrapGui() {
guiManager = new GuiManager(this);
guiManager.loadFromFolder(new File(getDataFolder(), "menus"));
}
private void bootstrapReloads() {
context.reload().register(new Reloadable() {
@Override
public String id() {
return "gui.menus";
}
@Override
public void reload() {
guiManager.reloadAll();
}
});
}
private void bootstrapHealthAndMetrics() {
context.health().register("myplugin.db", () -> {
try {
databaseManager.getDefault().withConnection(connection -> null);
return HealthStatus.up("database reachable");
} catch (Exception ex) {
return HealthStatus.down(ex.getMessage());
}
});
context.metrics().registerGauge("myplugin.online_players", () ->
(double) getServer().getOnlinePlayers().size()
);
}
private void shutdownDatabase() {
if (databaseManager != null) {
databaseManager.close();
}
}
}
Шаг 3. Опишите первую сущность
@AllArgsConstructor
@Getter
public class PlayerEntity {
@PrimaryKey
private final UUID playerId;
private final String nickname;
private final Integer coins;
private final Long lastSeenAt;
}
Пояснения:
@PrimaryKeyобозначает уникальный ключ записи.Integer/Long(а неint/long) удобны для nullable сценариев.- Конструктор должен корректно покрывать поля сущности.
Шаг 4. Добавьте базовые операции с БД
Например, при первом входе игрока:
public void ensureProfile(Player player) {
UUID id = player.getUniqueId();
players.upsert(
Map.of("playerId", id),
new PlayerEntity(id, player.getName(), 100, System.currentTimeMillis()),
Map.of("nickname", player.getName(), "lastSeenAt", System.currentTimeMillis())
);
}
Изменение баланса:
public boolean spendCoins(UUID playerId, int amount) {
Optional<PlayerEntity> profileOpt = players.findFirst()
.where("playerId", playerId)
.execute();
if (profileOpt.isEmpty()) return false;
PlayerEntity profile = profileOpt.get();
int current = profile.getCoins() == null ? 0 : profile.getCoins();
if (current < amount) return false;
players.update()
.set("coins", current - amount)
.where("playerId", playerId)
.execute();
return true;
}
Шаг 5. Создайте первое меню
Файл: plugins/MyPlugin/menus/main.yml
title: "&8Main"
size: 27
items:
reward:
material: DIAMOND
slot: 11
name: "&bПолучить награду"
lore:
- "&7Нажмите, чтобы выдать награду"
on_left_click:
- "message &aНаграда выдана"
- "playsound ENTITY_PLAYER_LEVELUP 1.0 1.0"
shop:
material: EMERALD
slot: 15
name: "&aОткрыть магазин"
on_left_click:
- "opengui shop"
Критично: используйте snake_case ключи (on_left_click, on_right_click, enchanted_when).
Шаг 6. Откройте GUI игроку
guiManager.openGui(player, "main");
"main" — это id по имени файла main.yml.
Шаг 7. Добавьте команду reload
public final class ReloadSubCommand implements SubCommand {
private final ReloadManager reloadManager;
public ReloadSubCommand(ReloadManager reloadManager) {
this.reloadManager = reloadManager;
}
@Override
public void onExecute(CommandSender sender, String[] args) {
ReloadReport report = reloadManager.reloadAll();
sender.sendMessage("Reload: ok=" + report.successful() + ", fail=" + report.failed());
}
@Override
public List<String> onTabComplete(CommandSender sender, String[] args) {
return List.of();
}
}
Практика: выводите отчёт reload пользователю, чтобы быстро понять, что реально перезагрузилось.
Шаг 8. Включите i18n для сообщений
Locale locale = Locale.forLanguageTag("ru-RU");
String msg = context.i18n().message(locale, "shop.purchase.success", Map.of("amount", 100));
player.sendMessage(msg);
Храните тексты в plugins/MyPlugin/i18n/*.properties.
Шаг 9. Добавьте базовую наблюдаемость
context.metrics().increment("myplugin.command.reload");
long nanos = context.metrics().time("myplugin.db.ensure_profile", () -> {
ensureProfile(player);
});
context.logger().info("profile_ensured", Map.of(
"player", player.getName(),
"durationNanos", nanos
));
Так вы заранее получаете диагностику и не ловите «слепые» баги в проде.
Шаг 10. Минимальный поток запуска
Проверка после старта:
- Плагин включился без исключений.
- БД файл/подключение создались.
- Таблица
playersпоявилась. - Меню
main.ymlоткрылось. - Reload команда работает.
Если любой шаг не работает, переходите в Troubleshooting.
Ошибки, которые встречаются чаще всего
- Нет JDBC драйвера в runtime.
- Неверное имя GUI id.
- Неправильные YAML-ключи actions.
- Отсутствует закрытие
DatabaseManagerна disable. - Конфликт инициализации (GUI/БД вызываются до bootstrap context).
Что делать после quick-start
Рекомендуемый порядок углубления:
- Dynamic Database — relations, include graphs, миграции.
- GUI API — кастомные actions/conditions, каталогизация меню.
- Validation API — стабильная валидация входных данных.
- Reload API — безопасный hot-reload.
- Observability API — операционная зрелость.
- Quests API — контентная механика прогресса.
Если пройти этот порядок, вы получите не просто «работающий код», а поддерживаемую архитектуру плагина.