Разработка плагинов для Minecraft 1.8 на Java 8

Практический курс по созданию плагинов для Minecraft 1.8 с использованием синтаксиса Java 8. Вы научитесь работать с API Bukkit/Spigot, создавать команды, обрабатывать события и управлять конфигурациями.

1. Основы Java 8 и структура плагина: главный класс и plugin.yml

Основы Java 8 и структура плагина: главный класс и plugin.yml

Приветствую! Если вы читаете эту статью, значит, ваша среда разработки IntelliJ IDEA уже настроена, JDK 8 установлен, и вы готовы написать свой первый код. В предыдущих этапах мы подготовили почву, а теперь пришло время заложить фундамент вашего будущего плагина.

В этой статье мы разберем анатомию плагина для Minecraft версии 1.8. Мы не просто напишем «Hello World», но и поймем, почему код пишется именно так. Мы изучим главный класс, наследование от JavaPlugin, жизненный цикл плагина и файл конфигурации plugin.yml.

Главный класс: Сердце плагина

Любой плагин для Bukkit/Spigot (ядра сервера Minecraft) начинается с главного класса. Это точка входа, место, где сервер «стучится» к вашему плагину, чтобы запустить его.

В Java 8, как и в любой другой версии Java, все строится вокруг классов и объектов. Чтобы сервер понял, что ваш код — это плагин, ваш главный класс должен наследовать функционал от родительского класса, предоставляемого API сервера.

!Схема наследования вашего класса от JavaPlugin

Наследование и JavaPlugin

Ключевое слово, которое делает ваш класс плагином — это extends. Взглянем на базовую структуру:

Разберем каждую строчку:

  • package ru.myserver.tutorial; — это «адрес» вашего класса. Пакеты в Java помогают группировать классы и избегать конфликтов имен. Обычно используется обратный домен (например, com.google или ru.yandex), но для обучения можно использовать что-то простое.
  • import org.bukkit.plugin.java.JavaPlugin; — мы сообщаем Java, где найти класс JavaPlugin. Он находится в библиотеке Spigot/Bukkit, которую мы подключили ранее.
  • public class Main extends JavaPlugin — мы объявляем публичный класс с именем Main (имя файла должно совпадать: Main.java) и говорим, что он расширяет (наследует) JavaPlugin.
  • > Важно: В одном плагине может быть только один класс, который наследует JavaPlugin. Если вы сделаете два таких класса, сервер выдаст ошибку при запуске.

    Жизненный цикл: onEnable и onDisable

    Когда сервер запускается или останавливается, он вызывает определенные методы вашего главного класса. В Java 8 мы используем аннотацию @Override, чтобы показать, что мы переопределяем стандартное поведение этих методов.

    Вот как выглядит минимальный рабочий код:

    Разбор методов:

    * onEnable(): Этот метод запускается сервером, когда плагин загружается. Это происходит при старте сервера или при команде /reload. Здесь вы будете регистрировать команды, слушатели событий (Events), загружать конфиги и подключаться к базам данных. * onDisable(): Этот метод запускается при выключении сервера или перезагрузке. Здесь нужно сохранять данные, закрывать соединения с базой данных и очищать память, если это необходимо. * getLogger().info(...): Это способ отправить сообщение в консоль сервера. getLogger() возвращает объект логгера, связанный с вашим плагином, а info пишет сообщение с уровнем важности «Информация».

    В Minecraft 1.8 консоль поддерживает цвета, но для системных сообщений лучше использовать стандартный логгер.

    Структура проекта в IntelliJ IDEA

    Чтобы ваш код превратился в рабочий .jar файл, он должен лежать в правильных папках. Стандартная структура проекта Maven выглядит так:

    !Правильная структура папок Maven проекта

    * src/main/java: Здесь живет ваш Java код. Внутри этой папки вы создаете структуру пакетов (папок), соответствующих вашему package (например, ru/myserver/tutorial). * src/main/resources: Здесь хранятся ресурсы — файлы, которые не являются кодом, но нужны плагину. Самый главный файл здесь — plugin.yml.

    plugin.yml: Паспорт вашего плагина

    Даже если вы напишете идеальный код на Java, сервер не запустит его без файла plugin.yml. Этот файл находится в корне вашего .jar архива (в исходниках он лежит в src/main/resources).

    plugin.yml — это файл в формате YAML. Это язык сериализации данных, который очень чувствителен к отступам.

    Минимальное содержание plugin.yml

    Для работы плагина необходимы три параметра:

    Детальный разбор полей:

  • name: Имя вашего плагина. Оно должно быть уникальным в рамках сервера. Не используйте пробелы (лучше MyPlugin, а не My Plugin). Это имя будет отображаться в списке /plugins.
  • version: Версия вашего плагина. Можно использовать любую нумерацию, например 1.0, v1.0-BETA и т.д.
  • main: Самое важное поле. Это полный путь к вашему главному классу (включая пакет). Сервер читает эту строку, чтобы понять, какой класс запускать.
  • Если в вашем классе Main пакет указан как package ru.myserver.tutorial;, то в plugin.yml вы обязаны написать ru.myserver.tutorial.Main.

    Расширенные настройки (Опционально)

    Вы можете добавить больше информации:

    Опасности YAML

    > YAML не прощает ошибок с отступами. Никогда не используйте клавишу TAB для отступов в plugin.yml! Используйте только пробелы.

    Если вы поставите TAB, сервер при запуске выдаст ошибку: org.bukkit.plugin.InvalidDescriptionException: Invalid plugin.yml.

    Сборка проекта (Компиляция)

    Вы написали код в классе Main и создали plugin.yml. Теперь нужно превратить это в файл, который понимает сервер Minecraft — в .jar файл.

    В IntelliJ IDEA с использованием Maven это делается просто:

  • Откройте панель Maven (обычно справа).
  • Найдите раздел Lifecycle.
  • Дважды кликните по package.
  • Maven начнет процесс сборки: он скачает необходимые библиотеки (если их еще нет), скомпилирует ваш Java код в байт-код, положит ресурсы и упакует все в архив.

    После успешной сборки (вы увидите сообщение BUILD SUCCESS в консоли IDEA), готовый файл появится в папке target внутри папки вашего проекта.

    Практика: Ваш первый запуск

    Давайте подытожим алгоритм действий, который вы должны выполнить после прочтения этой статьи:

  • Создать класс Main в папке src/main/java/....
  • Унаследовать его от JavaPlugin.
  • Переопределить метод onEnable и написать туда вывод в лог.
  • Создать файл plugin.yml в папке src/main/resources.
  • Заполнить name, version и main.
  • Запустить Maven задачу package.
  • Скопировать полученный .jar файл из папки target в папку plugins вашего тестового сервера.
  • Запустить сервер и увидеть заветное сообщение в консоли.
  • Особенности Java 8 в контексте Minecraft 1.8

    Поскольку наш курс ориентирован на версию 1.8, мы используем Java 8. Это «золотой стандарт» для старых версий майнкрафта. Хотя современные версии Java (17, 21) имеют много новых фишек (например, var, record, текстовые блоки), в Java 8 их нет.

    Однако, Java 8 подарила нам Лямбда-выражения и Stream API, которые мы будем активно использовать в будущих уроках для обработки коллекций игроков и инвентарей. Пока что запомните: синтаксис должен быть строгим, типы переменных указываются явно.

    В следующем уроке мы научимся реагировать на действия игроков: обрабатывать вход на сервер, ломание блоков и чат. Но без понимания структуры главного класса и plugin.yml двигаться дальше невозможно.

    Убедитесь, что ваш «Hello World» плагин успешно запускается на сервере, прежде чем переходить к следующей статье.

    2. Система событий (Events): регистрация слушателей и взаимодействие с миром

    Система событий (Events): регистрация слушателей и взаимодействие с миром

    В предыдущей статье мы создали «скелет» плагина: главный класс и файл plugin.yml. Наш плагин уже умеет запускаться и выключаться, выводя сообщения в консоль. Но давайте будем честны: плагин, который просто пишет «Привет» в консоль при старте, никому не интересен.

    Настоящая магия начинается тогда, когда ваш код начинает реагировать на действия игроков. Игрок зашел на сервер? Мы хотим выдать ему приветственное сообщение. Кто-то сломал алмазный блок? Мы хотим запретить это или оповестить администратора. Игрок написал в чат? Мы хотим изменить формат сообщения.

    Для всего этого в Bukkit API существует мощная система событий (Event System). В этой статье мы разберем, как она устроена, как создавать «слушателей» и как управлять игровым миром через события.

    Что такое событие (Event)?

    С точки зрения программирования, система событий в Minecraft реализует паттерн проектирования Observer (Наблюдатель).

    Представьте, что сервер — это радиостанция. Когда в игре что-то происходит (вход игрока, удар по мобу, изменение погоды), сервер посылает сигнал — «Событие». Ваш плагин — это радиоприемник. Если он настроен на правильную волну (зарегистрировал слушатель), он поймает этот сигнал и выполнит код.

    !Схема распространения события от ядра сервера к вашему плагину

    В Java каждое событие — это отдельный класс. Например:

    * PlayerJoinEvent — игрок зашел на сервер. * BlockBreakEvent — блок разрушен. * AsyncPlayerChatEvent — отправлено сообщение в чат. * EntityDamageByEntityEvent — одна сущность ударила другую.

    Создание первого слушателя (Listener)

    Чтобы ваш плагин начал реагировать на события, нужно создать класс, который реализует интерфейс Listener. Хорошей практикой считается не писать весь код в главном классе Main, а создавать отдельные классы для обработки событий.

    Создадим новый класс JoinHandler (или EventsHandler) в том же пакете, где лежит ваш Main.

    Шаг 1: Реализация интерфейса

    Мы указали implements Listener. Это маркерный интерфейс, он не требует реализации каких-либо методов, но сообщает Bukkit API, что этот класс предназначен для обработки событий.

    Шаг 2: Создание метода-обработчика

    Теперь напишем метод, который будет срабатывать при входе игрока.

    Разберем этот код детально:

  • @EventHandler: Это самая важная часть. Без этой аннотации сервер проигнорирует ваш метод, даже если все остальное написано верно. Она говорит Bukkit'у: «Эй, этот метод нужно запустить, когда произойдет событие».
  • public void onPlayerJoin: Название метода может быть любым (onJoin, helloPlayer, run), но оно должно быть публичным и возвращать void.
  • (PlayerJoinEvent event): В аргументах метода мы указываем, какое именно событие мы хотим слушать. Сервер автоматически передаст объект события в этот метод.
  • event.getPlayer(): Почти все события, связанные с игроками, позволяют получить объект Player. Через него мы узнаем имя, здоровье, инвентарь и многое другое.
  • event.setJoinMessage(...): Мы меняем стандартное сообщение о входе (желтый текст) на свое. Символ § (параграф) используется в Minecraft 1.8 для цветовых кодов (§e — желтый).
  • Регистрация слушателя

    Мы написали класс слушателя, но сервер о нем еще не знает. Плагин не сканирует ваши классы автоматически. Вы должны явно зарегистрировать слушателя в главном классе при включении плагина.

    Вернемся в класс Main и изменим метод onEnable():

    Строка Bukkit.getPluginManager().registerEvents(new JoinHandler(), this); делает всю работу: * Первый аргумент (new JoinHandler()) — это экземпляр вашего класса со слушателями. * Второй аргумент (this) — это ссылка на ваш главный класс плагина, чтобы сервер знал, какому плагину принадлежит этот слушатель.

    > Частая ошибка новичков: Написать код события, поставить @EventHandler, но забыть прописать registerEvents в onEnable. В таком случае код просто не будет работать, и ошибок в консоли не будет.

    Отмена событий (Cancellable)

    Многие события в Minecraft можно отменить. Это реализуется через интерфейс Cancellable. Если вы отмените событие, сервер сделает вид, что действия никогда не было.

    Давайте запретим игрокам ломать блоки бедрока (хотя они и так не ломаются в выживании, но в креативе это возможно). Добавим новый метод в наш класс JoinHandler (или создадим новый класс BlockHandler, что было бы чище).

    Ключевые моменты: * event.getBlock(): Получаем блок, который участвует в событии. * getType(): Получаем тип материала (Material.BEDROCK, Material.DIAMOND_BLOCK и т.д.). В Java 8 и Bukkit 1.8 используются enum Material. * event.setCancelled(true): Это команда серверу отменить действие. Блок визуально исчезнет на долю секунды (клиентская предсказательная логика), а затем появится снова, так как сервер запретил разрушение.

    Приоритет событий (Event Priority)

    Иногда на сервере установлено много плагинов. Что если один плагин хочет разрешить действие, а другой — запретить? Или вы хотите изменить сообщение в чате уже после того, как другой плагин его отформатировал?

    Для этого у аннотации @EventHandler есть параметр priority.

    Существует 6 уровней приоритета:

  • LOWEST (Самый низкий) — срабатывает первым.
  • LOW
  • NORMAL (По умолчанию)
  • HIGH
  • HIGHEST
  • MONITOR (Самый высокий) — срабатывает последним.
  • События выполняются по цепочке от LOWEST к MONITOR.

    Важно: Тот, кто срабатывает позже, имеет последнее слово. Если плагин на LOW отменил событие, а плагин на HIGH его снова разрешил (setCancelled(false)), то событие произойдет.

    > Правило: Используйте MONITOR только для чтения результатов события (например, для логирования). Не изменяйте и не отменяйте события на приоритете MONITOR, так как другие плагины будут считать, что событие уже завершено.

    Практика: Взаимодействие с миром

    Давайте напишем небольшой функционал: когда игрок наступает на золотой блок, его подбрасывает вверх.

    Для этого нам понадобится событие PlayerMoveEvent. Будьте осторожны с ним: оно вызывается каждый раз, когда игрок сдвигается хотя бы на миллиметр или поворачивает голову. Тяжелый код в этом событии может вызвать лаги.

    Здесь мы использовали класс Vector для работы с физикой движения. setVelocity задает скорость и направление полета сущности.

    Особенности Java 8 при работе с событиями

    В Java 8 мы часто используем лямбда-выражения, но классический Bukkit API в версии 1.8 не поддерживает регистрацию событий через лямбды «из коробки» (без сторонних библиотек). Поэтому мы используем стандартный подход с методами и аннотациями.

    Однако, внутри методов событий вы можете и должны использовать возможности Java 8. Например, если вы хотите отфильтровать список предметов в инвентаре при открытии сундука, Stream API будет как нельзя кстати.

    Заключение

    Система событий — это нервная система вашего плагина. Она позволяет реагировать на все, что происходит в мире Minecraft.

    Краткий чек-лист для создания события:

  • Создать класс, реализующий Listener.
  • Написать метод public void, принимающий событие.
  • Поставить аннотацию @EventHandler над методом.
  • Зарегистрировать класс в onEnable через PluginManager.
  • В следующем уроке мы научимся управлять сервером активно, а не пассивно: мы разберем создание Команд и обработку аргументов, чтобы игроки могли сами вызывать функции вашего плагина.

    3. Создание и обработка команд: CommandExecutor и аргументы

    Создание и обработка команд: CommandExecutor и аргументы

    В предыдущих статьях мы научили наш плагин «слушать» мир: реагировать на вход игроков, разрушение блоков и другие события. Это пассивное взаимодействие. Но что, если мы хотим, чтобы плагин выполнял действие по прямому приказу игрока или администратора? Здесь на сцену выходят Команды.

    Команды — это основной способ активного взаимодействия пользователя с плагином. Будь то /gamemode 1, /spawn или /region claim — всё это работает через единый механизм, который мы сегодня разберем. Мы научимся регистрировать команды, обрабатывать их в отдельном классе и работать с аргументами.

    Анатомия команды в Bukkit API

    Когда игрок вводит в чат сообщение, начинающееся со слэша /, сервер понимает, что это не просто текст, а директива к действию. Процесс обработки команды выглядит следующим образом:

    !Путь команды от чата игрока до вашего Java-кода

    Чтобы ваш плагин мог обработать команду, необходимо выполнить два обязательных условия:

  • Объявить команду в файле plugin.yml.
  • Написать Java-код для её обработки.
  • Шаг 1: Регистрация в plugin.yml

    Сервер Minecraft не умеет сканировать ваш код в поисках команд. Вы должны явно заявить: «Мой плагин отвечает за команду /tutorial». Это делается в файле plugin.yml.

    Добавьте секцию commands в ваш файл конфигурации:

    Разбор параметров:

    * hello / heal: Сами названия команд (без слэша). * description: Краткое описание (отображается в /help). * usage: Сообщение, которое будет показано игроку, если метод обработки вернет false. * aliases: Список альтернативных имен. Например, ввод /hi вызовет тот же код, что и /hello. * permission: (Опционально) Право, необходимое для использования. Если у игрока его нет, команда даже не дойдет до вашего кода. * permission-message: Сообщение при отсутствии прав.

    > Важно: Если вы не пропишете команду в plugin.yml, при попытке её ввода сервер скажет Unknown command, даже если код написан идеально.

    Шаг 2: Интерфейс CommandExecutor

    Можно обрабатывать команды прямо в главном классе Main, переопределив метод onCommand. Однако, если команд много, главный класс превратится в «кашу». В Java принято разделять ответственность.

    Мы создадим отдельный класс для обработки команд, который будет реализовывать интерфейс CommandExecutor.

    Создайте новый класс HealCommand:

    Разбор аргументов метода onCommand

    Метод onCommand принимает четыре важнейших параметра:

  • CommandSender sender: Тот, кто отправил команду. Это может быть Игрок (Player), Консоль (ConsoleCommandSender) или даже Командный блок (BlockCommandSender).
  • Command command: Объект самой команды, содержащий информацию из plugin.yml (описание, usage и т.д.).
  • String label: Точное слово, которое ввел игрок. Если команда /hello имеет алиас /hi, и игрок ввел /hi, то label будет равен "hi".
  • String[] args: Массив аргументов. Это всё, что написано после команды через пробел.
  • Возвращаемое значение (boolean)

    Метод обязан вернуть true или false: * true: Команда выполнена успешно (или ошибка обработана нами вручную). * false: Команда использована неверно. Сервер автоматически отправит отправителю сообщение из поля usage в plugin.yml.

    Шаг 3: Реализация логики (Команда /heal)

    Давайте напишем логику для команды лечения. Нам нужно учесть, что команду может отправить консоль (у которой нет здоровья), и что игрок может захотеть вылечить себя или другого игрока.

    Важные моменты безопасности

  • Проверка типа отправителя (instanceof): Никогда не делайте приведение типов (Player) sender без проверки. Если команду введут из консоли сервера, ваш плагин выдаст ошибку ClassCastException.
  • Проверка на null: Метод Bukkit.getPlayer(String name) возвращает null, если игрок не в сети. Всегда проверяйте это перед использованием методов игрока.
  • Массивы: При обращении к args[0], args[1] и т.д. всегда сначала проверяйте args.length. Иначе получите ArrayIndexOutOfBoundsException.
  • Шаг 4: Связывание Executor с Главным классом

    Мы написали класс HealCommand и прописали команду в plugin.yml. Остался последний штрих — соединить их. Это делается в методе onEnable главного класса.

    Если вы забудете эту строчку, при вводе команды ничего не произойдет (или сработает стандартный обработчик Bukkit, который ничего не делает).

    Работа с подкомандами (Subcommands)

    Часто плагины имеют одну основную команду и множество подкоманд, например: /clan create, /clan invite, /clan kick. В Bukkit нет специального понятия «подкоманда». Это просто первый аргумент массива args.

    Пример структуры для сложной команды:

    В Java 8 (которую мы используем для 1.8) конструкция switch отлично работает со строками, что делает код чистым и читаемым.

    TabCompleter: Автодополнение

    В версиях 1.8 и выше игроки привыкли нажимать TAB для автодополнения ников и команд. Чтобы ваш плагин поддерживал это, нужно реализовать интерфейс TabCompleter.

    И не забудьте зарегистрировать его в Main:

    Заключение

    Команды — это мощный инструмент управления сервером. Правильное использование CommandExecutor позволяет держать код чистым и структурированным.

    Краткий чек-лист при создании команды:

  • Придумать название и аргументы.
  • Записать в plugin.yml.
  • Создать класс implements CommandExecutor.
  • Реализовать onCommand, не забывая про проверки sender и args.length.
  • Зарегистрировать в onEnable через setExecutor.
  • В следующей статье мы разберем работу с конфигурационными файлами (config.yml), чтобы администраторы могли настраивать ваши сообщения и параметры без пересборки плагина.

    4. Работа с файлами конфигурации config.yml и сохранение данных

    Работа с файлами конфигурации config.yml и сохранение данных

    Приветствую, коллеги! Мы уже научились создавать структуру плагина, слушать события и обрабатывать команды. Ваш плагин уже что-то умеет, но он негибкий. Представьте, что вы написали плагин на приветственное сообщение, и в коде жестко прописали: "Привет, игрок!".

    Что, если владелец сервера захочет изменить это сообщение на "Добро пожаловать на сервер!"? Ему придется просить вас переписать код и пересобрать .jar файл. Это неудобно и непрофессионально.

    Сегодня мы изучим файлы конфигурации. Мы научимся выносить настройки из Java-кода в удобный файл config.yml, читать оттуда данные, использовать цветовые коды и даже сохранять информацию обратно в файл.

    Что такое config.yml и формат YAML

    Bukkit API использует формат YAML (YAML Ain't Markup Language) для хранения настроек. Это тот же формат, что и в plugin.yml. Он человекочитаем, прост и строг к отступам.

    Файл конфигурации представляет собой набор пар «Ключ: Значение». Данные могут быть вложенными, образуя древовидную структуру.

    Пример типичного config.yml:

    В этом примере: * messages — это секция (раздел). * join — это ключ внутри секции messages. * "&aДобро пожаловать..." — это строковое значение. * allowed-items — это список строк.

    !Схема потока данных между файлом конфигурации и плагином

    Создание конфигурации по умолчанию

    Прежде чем читать настройки, файл должен физически появиться в папке плагина (plugins/MyPlugin/config.yml). Изначально он существует только внутри вашего .jar файла в папке src/main/resources.

    Шаг 1: Создание файла

    В IntelliJ IDEA, в папке src/main/resources, создайте файл config.yml. Наполните его базовыми настройками, которые будут использоваться, если администратор ничего не менял.

    Шаг 2: Копирование файла при запуске

    В главном классе вашего плагина (в методе onEnable) нужно сказать Bukkit'у: «Если конфига еще нет в папке сервера, скопируй туда мой стандартный файл».

    Для этого используется метод saveDefaultConfig().

    > Важно: Метод saveDefaultConfig() не перезаписывает файл, если он уже существует. Так что настройки, измененные администратором, не пропадут при перезагрузке.

    Чтение данных из конфигурации

    Класс JavaPlugin предоставляет метод getConfig(), который возвращает объект FileConfiguration. Через этот объект мы и получаем доступ к значениям.

    Основные методы чтения: * getString("путь.к.значению") — возвращает строку. * getInt("путь") — возвращает целое число. * getBoolean("путь") — возвращает true или false. * getDouble("путь") — возвращает дробное число. * getStringList("путь") — возвращает список строк (List<String>).

    Пример использования в команде

    Давайте модифицируем нашу команду лечения из прошлого урока, чтобы она использовала настройки из конфига.

    Предположим, наш config.yml выглядит так:

    Теперь код обработчика команды:

    Не забудьте обновить регистрацию команды в Main, передав this: getCommand("heal").setExecutor(new HealCommand(this));

    Работа с цветами (Color Codes)

    В файлах config.yml администраторы привыкли использовать символ амперсанда & для цветов (например, &c для красного), потому что символ параграфа § сложно ввести с клавиатуры. Однако Minecraft понимает только §.

    Вам нужно заменять & на § при чтении строк. В Bukkit API для этого есть удобный метод:

    Этот метод безопаснее, чем простой replace, так как он корректно обрабатывает только валидные цветовые коды.

    Перезагрузка конфигурации (Reload)

    Когда администратор меняет config.yml, изменения не вступают в силу мгновенно. Сервер загружает файл в оперативную память только один раз — при старте. Чтобы обновить данные в памяти без перезагрузки всего сервера, нужно использовать метод reloadConfig().

    Давайте создадим простую команду /myplugin reload.

    После вызова reloadConfig(), все последующие вызовы getConfig().getString(...) будут возвращать уже новые значения из файла.

    Запись и сохранение данных

    Иногда плагину нужно не только читать настройки, но и сохранять данные. Например, записать координаты дома игрока или количество убитых мобов.

    > Примечание: Для хранения больших объемов данных (статистика тысяч игроков) лучше использовать базы данных (MySQL, SQLite). Но для простых вещей config.yml (или отдельный .yml файл) вполне подходит.

    Изменение значений в памяти

    Для записи используется метод set(String path, Object value).

    Сохранение на диск

    Метод set меняет данные только в оперативной памяти Java. Если сервер выключится, данные пропадут. Чтобы записать их в файл, нужно вызвать saveConfig().

    Важный нюанс: При вызове saveConfig() все комментарии в файле config.yml (строки, начинающиеся с #) будут удалены! Это особенность библиотеки Bukkit YAML. Поэтому обычно config.yml используют только для настроек (read-only), а для хранения данных создают отдельные файлы (например, data.yml), где комментарии не важны.

    Работа с путями и проверками

    Иногда администратор может допустить ошибку и удалить нужную строку из конфига. Если вы попытаетесь получить значение, которого нет, вы можете получить null или значение по умолчанию.

    Вы можете указать значение по умолчанию прямо в коде:

    Также полезно проверять наличие пути:

    Частые ошибки при работе с YAML

  • Табуляция (TAB): YAML категорически запрещает использование символа табуляции для отступов. Используйте только пробелы (обычно 2 или 4 пробела). Если в файле будет хоть один TAB, плагин выдаст ошибку SnakeYAML при запуске.
  • Кодировка: Всегда сохраняйте файлы .yml в кодировке UTF-8. Иначе русские буквы превратятся в «кракозябры».
  • Списки: Будьте внимательны с форматом списков.
  • В Java это читается через getStringList("list").

    Заключение

    Теперь ваш плагин стал по-настоящему настраиваемым. Вы умеете:

  • Создавать и загружать config.yml.
  • Получать из него строки, числа и списки.
  • Обрабатывать цвета.
  • Перезагружать настройки на лету.
  • Сохранять простые данные.
  • Конфигурация — это мост между разработчиком и пользователем (администратором сервера). Делайте этот мост удобным: давайте понятные названия ключам и устанавливайте разумные значения по умолчанию.

    В следующей статье мы перейдем к одной из самых сложных, но интересных тем: Планировщики (Schedulers) и Runnable. Мы научимся выполнять код с задержкой, создавать таймеры и повторяющиеся задачи, не останавливая основной поток сервера.

    5. Планировщики задач и циклы: использование BukkitRunnable

    Планировщики задач и циклы: использование BukkitRunnable

    Приветствую, разработчики! В прошлых уроках мы научились реагировать на события и сохранять данные в конфигурацию. Но пока что наш код работает «здесь и сейчас». Игрок ввел команду — плагин мгновенно ответил. Игрок зашел — плагин сразу отправил сообщение.

    Однако в геймдеве время — это ключевой ресурс. Что, если вы хотите запустить отсчет до начала мини-игры? Или удалять выброшенные предметы каждые 5 минут? Или создать эффект частиц, который крутится вокруг игрока?

    Для этого нам нужны планировщики задач (Schedulers). В этой статье мы разберем, почему в Minecraft нельзя использовать обычные циклы while и Thread.sleep, что такое «тик» сервера, и как правильно использовать класс BukkitRunnable.

    Главный поток и понятие Тика (Tick)

    Сервер Minecraft — это однопоточное приложение (в основном). Это означает, что вся логика игры — движение мобов, обработка физики, рост травы, выполнение команд и работа плагинов — происходит в одном главном цикле, который называется Main Thread.

    Единица времени в Minecraft называется Тик (Tick). Сервер стремится выполнять 20 тиков в секунду (TPS — Ticks Per Second).

    !Визуализация цикла обновления сервера Minecraft, где 20 тиков составляют одну секунду.

    Математически длительность одного тика можно выразить так:

    Где — длительность одного тика в миллисекундах, — количество миллисекунд в секунде, а — целевое количество тиков в секунду (TPS).

    Почему нельзя использовать Thread.sleep?

    Новички часто пытаются сделать задержку так:

    Если вы напишете такой код, вы остановите весь сервер. Поскольку сервер работает в одном потоке, Thread.sleep(1000) заставит замереть всё: других игроков, мобов, физику воды. Для всех на сервере игра просто зависнет на секунду. Это называется «лаг».

    Вместо остановки потока, мы должны «попросить» сервер выполнить код позже. Для этого и нужен Bukkit Scheduler.

    Знакомство с BukkitRunnable

    В Bukkit API есть два способа работы с задачами: через Bukkit.getScheduler() и через абстрактный класс BukkitRunnable. Мы сосредоточимся на втором, так как он более объектно-ориентирован и удобен для управления задачами (например, для их отмены).

    BukkitRunnable — это класс, который реализует интерфейс Runnable. Вы создаете его экземпляр, переопределяете метод run(), а затем говорите ему, как именно запуститься.

    1. Отложенная задача (RunTaskLater)

    Используется, когда нужно выполнить код один раз, но с задержкой. Время указывается в тиках.

    Формула перевода секунд в тики:

    Где — количество тиков, — количество секунд, а — константа TPS.

    Пример: Удалить сообщение через 5 секунд (100 тиков).

    Обратите внимание на .runTaskLater(plugin, 100L). * Первый аргумент — экземпляр вашего главного класса (плагина). Без него планировщик не будет работать (это защита от утечек памяти при выключении плагина). * Второй аргумент — задержка в тиках (long).

    2. Повторяющаяся задача (RunTaskTimer)

    Это аналог цикла. Задача запускается, ждет, выполняется, снова ждет и так до бесконечности (или пока мы её не остановим).

    Метод .runTaskTimer(plugin, delay, period) принимает три аргумента:

  • Плагин.
  • delay — задержка перед первым запуском (в тиках).
  • period — интервал между повторами (в тиках).
  • Пример: Обратный отсчет от 5 до 1.

    Запуск этой задачи из команды:

    Метод this.cancel() — это главное преимущество BukkitRunnable. Он позволяет задаче самой себя остановить.

    Синхронные и Асинхронные задачи

    До сих пор мы говорили о синхронных задачах. Они выполняются в главном потоке сервера, строго по очереди с обработкой блоков и мобов. Это безопасно для взаимодействия с API Minecraft.

    Однако, иногда нам нужно выполнить тяжелую работу: сложный математический расчет, запрос к веб-сайту или обращение к базе данных MySQL. Если сделать это в главном потоке, сервер зависнет на время ожидания ответа от базы данных.

    Для этого существуют асинхронные задачи (runTaskAsynchronously, runTaskTimerAsynchronously). Они запускаются в отдельном потоке процессора.

    Золотое правило Bukkit API

    > Никогда не обращайтесь к Bukkit API из асинхронного потока!

    Вы не можете ставить блоки, спавнить мобов, кикать игроков или менять их инвентарь асинхронно. Это приведет к ошибкам, крашам сервера или повреждению карты.

    Как правильно работать с асинхронностью:

  • Собрать данные (синхронно).
  • Запустить асинхронную задачу для тяжелых вычислений.
  • Внутри асинхронной задачи, когда результат готов, создать новую синхронную задачу, чтобы применить результат в игре.
  • Пример правильной цепочки:

    Особенности Java 8: Лямбды

    Хотя BukkitRunnable — это абстрактный класс, и для него мы используем анонимные классы (как в примерах выше), обычный планировщик Bukkit.getScheduler() принимает интерфейс Runnable. Это значит, что для простых одноразовых задач в Java 8 можно использовать лямбда-выражения, что делает код короче.

    Однако, внутри лямбды у вас нет доступа к методу this.cancel(), поэтому для повторяющихся таймеров лучше использовать классический BukkitRunnable.

    Практика: Создание эффекта регенерации

    Давайте напишем задачу, которая будет лечить игрока на 1 сердце каждые 2 секунды, пока его здоровье не станет полным.

    Запуск в команде /healovertime: new RegenTask(player).runTaskTimer(this, 0L, 40L); (40 тиков = 2 секунды).

    Заключение

    Планировщики задач — это сердце динамичного геймплея. Они позволяют растягивать действия во времени, создавать таймеры, кулдауны и периодические проверки.

    Главное, что нужно запомнить:

  • 1 секунда = 20 тиков.
  • Никогда не используйте Thread.sleep().
  • Используйте BukkitRunnable для создания таймеров.
  • Не трогайте Bukkit API в асинхронных задачах.
  • В следующей статье мы углубимся в работу с инвентарями и предметами: научимся создавать GUI-меню, которые так популярны на серверах с мини-играми.