Java для разработки плагинов Minecraft (Spigot/Paper)

Курс поможет освоить основы Java и применить их для создания плагинов под Minecraft-серверы на базе Spigot/Paper. Вы научитесь настраивать среду разработки, работать с API, обрабатывать события, хранить данные и собирать/публиковать плагины.

1. База Java для плагинов: синтаксис, ООП, коллекции

База Java для плагинов: синтаксис, ООП, коллекции

Зачем база Java именно для Spigot/Paper

Плагины для Minecraft на Spigot/Paper — это обычные Java-приложения, которые запускаются внутри сервера. Сервер вызывает ваш код (например, при вводе команды или событии), а вы используете Java, чтобы:

  • Хранить состояние плагина (настройки, кэши, кулдауны, данные игроков)
  • Обрабатывать события (вход игрока, ломание блока, урон)
  • Реагировать на команды
  • Работать с коллекциями данных и файлами
  • В следующих статьях мы начнём писать реальный плагин, а здесь закладываем фундамент: синтаксис Java, ООП и коллекции — ровно то, что вы будете использовать каждый день.

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

    Версия Java и стиль кода

    На современных версиях Paper обычно используется Java 17 и выше. Конкретные требования зависят от версии сервера.

  • Ориентируйтесь на актуальные требования в Документации Paper
  • Синтаксис в этой статье будет совместим с Java 17
  • Синтаксис Java, который постоянно встречается в плагинах

    Переменные и типы

    В Java тип указывается явно.

    Часто в плагинах встречаются:

  • int, long для счётчиков, времени в миллисекундах
  • boolean для флагов
  • String для сообщений, имён
  • UUID для идентификации игроков
  • Методы

    Метод — это именованный блок кода. В плагинах вы постоянно выносите повторяющиеся действия в методы.

    Условия и циклы

    События и команды обычно содержат ветвления.

    Циклы часто используются для перебора коллекций.

    null и проверки

    В Java ссылочная переменная может быть null (то есть ничего не указывает). В плагинах null часто появляется при поиске сущности по имени/UUID или при чтении конфигов.

    Если API возвращает Optional, это явный сигнал: значение может отсутствовать.

    Классы и объекты: как устроен ваш код

    Класс — это шаблон. Объект — экземпляр класса.

    Поля, конструктор, инкапсуляция

    Инкапсуляция — прячем внутреннее состояние и даём доступ через методы.

    Ключевые моменты для плагинов:

  • private защищает поля от случайной порчи из других классов
  • Публичные методы (public) — это ваш контракт использования
  • static — когда это уместно

    static принадлежит классу, а не объекту.

    В плагинах static применяют для:

  • Утилитных методов
  • Констант
  • Но состояние плагина (Map с данными игроков и т. п.) обычно делают полями экземпляра главного класса/сервиса, а не static, чтобы проще управлять жизненным циклом.

    Модификаторы доступа

  • public — доступно отовсюду
  • protected — доступно в пакете и наследникам
  • (package-private) — без модификатора, доступно только в пакете
  • private — доступно только внутри класса
  • ООП в контексте плагинов

    ООП нужно не ради терминов, а ради структуры: чтобы код не превращался в один огромный класс.

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

    Наследование — это is-a ("является").

    Пример из Bukkit/Paper: ваш главный класс плагина наследуется от JavaPlugin.

    Важно:

  • Наследование стоит использовать, когда это требует API (как с JavaPlugin)
  • Для своей логики чаще предпочтительнее композиция (см. ниже)
  • Интерфейсы

    Интерфейс — это контракт: что объект умеет делать.

    В плагинах интерфейсы встречаются постоянно, например обработчики событий реализуют Listener.

    Полиморфизм

    Полиморфизм — это когда код работает через общий тип (интерфейс/родительский класс), а конкретная реализация подставляется автоматически.

    В плагинах это полезно для:

  • Хранилищ данных (в памяти, в YAML, в базе)
  • Разных стратегий форматирования сообщений
  • Композиция вместо наследования

    Композиция — это has-a ("имеет"). Обычно так строят плагины: главный класс имеет сервисы.

    Это делает код проще для тестирования и расширения.

    !Схема показывает, как в плагинах сочетаются наследование от JavaPlugin и композиция сервисов, а также интерфейс с реализациями

    Исключения: как не убить сервер ошибками

    Исключение (exception) — это сигнал об ошибке выполнения.

    try/catch и логирование

    Практика для плагинов:

  • Не глотайте исключения пустым catch
  • Логируйте достаточно, чтобы понять причину
  • Проверяйте входные данные команд (аргументы, диапазоны)
  • Checked и unchecked (коротко и по делу)

  • Unchecked (RuntimeException и наследники) — частые ошибки логики или данных
  • Checked (IOException и т. п.) — заставляют обработать/пробросить, часто встречаются при работе с файлами
  • Дженерики (Generics): типобезопасные коллекции

    Дженерики позволяют коллекциям знать тип элементов.

    Почему это критично для плагинов:

  • Меньше ClassCastException в рантайме
  • IDE лучше подсказывает методы
  • Код читабельнее
  • Коллекции: ваш основной инструмент хранения данных

    Коллекции — это структуры данных из Java Collections Framework.

    Официальные материалы:

  • Oracle Tutorial: Language Basics
  • Oracle: Collections Framework Overview
  • Java SE 17 API
  • List: упорядоченный список

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

    Частые применения в плагинах:

  • Список строк для вывода
  • Список задач/действий
  • Set: уникальные элементы

    Используйте, когда важна уникальность.

    Частые применения:

  • Список миров, где функция отключена
  • Уникальные UUID игроков в режиме ожидания
  • Map: ключ → значение

    Самая полезная коллекция для плагинов: быстрый доступ к данным по ключу.

    Типичные кейсы:

  • UUID -> PlayerData
  • UUID -> cooldownEndTime
  • String -> настройка/локализация
  • Выбор реализации: что брать по умолчанию

  • ArrayList — почти всегда базовый выбор для List
  • HashSet — базовый выбор для Set
  • HashMap — базовый выбор для Map
  • Если нужен порядок вставки:

  • LinkedHashMap, LinkedHashSet
  • Если нужен отсортированный порядок:

  • TreeMap, TreeSet
  • Если ключ — это enum:

  • EnumMap (быстро и экономно)
  • Итерация по коллекциям

    equals() и hashCode() — важно для HashMap и HashSet

    HashMap и HashSet полагаются на:

  • equals() чтобы понять, равны ли два ключа
  • hashCode() чтобы быстро находить "корзину" для ключа
  • Если вы делаете свой класс ключом в HashMap или элементом в HashSet, обычно нужно переопределить оба.

    Практическая рекомендация:

  • Делайте такие ключи неизменяемыми (final поля, без сеттеров), иначе можно "сломать" поиск в HashMap
  • Потоки выполнения и коллекции в плагинах

    Большая часть Bukkit/Paper API должна вызываться из главного потока сервера. Это влияет на коллекции:

  • Если вы меняете HashMap только в главном потоке, обычно всё нормально
  • Если вы запускаете асинхронные задачи и они трогают общие структуры, появляется риск гонок
  • Базовые правила:

  • Не вызывайте Bukkit/Paper API из асинхронного потока без явного разрешения API
  • Если данные обновляются из разных потоков, используйте синхронизацию или потокобезопасные структуры (например, ConcurrentHashMap) и проектируйте доступ аккуратно
  • Мини-практика: типичные фрагменты для плагина

    Кулдаун на команду

    Набор отключённых миров

    Хранилище данных игрока

    Итоги

    В этой статье вы собрали базу, без которой плагин быстро превращается в хаос:

  • Синтаксис (переменные, методы, ветвления, null)
  • Классы и модификаторы доступа
  • ООП в практическом виде (наследование, интерфейсы, композиция)
  • Исключения и базовая обработка ошибок
  • Дженерики и коллекции (List, Set, Map) как основной способ хранить состояние плагина
  • В следующей статье мы перейдём от базы к практике: создадим каркас плагина, разберём структуру проекта и жизненный цикл onEnable/onDisable, чтобы начать подключать команды и события.

    2. Среда разработки: JDK, IDE, Maven/Gradle, Git

    Среда разработки: JDK, IDE, Maven/Gradle, Git

    Зачем вообще настраивать среду заранее

    В прошлой статье вы разобрали базу Java: синтаксис, ООП и коллекции. Следующий шаг перед написанием реального плагина под Spigot/Paper — собрать рабочую среду, чтобы:

  • Писать код с подсказками IDE и быстрым поиском по API
  • Собирать .jar плагина одной командой
  • Подключать Paper API как зависимость
  • Версионировать проект и безопасно откатываться к рабочим версиям
  • Эта статья проведёт вас через четыре кирпича: JDK, IDE, Maven/Gradle, Git.

    !Диаграмма цикла разработки плагина от кода до проверки на сервере

    JDK: что это и какую версию ставить

    JDK (Java Development Kit) — это набор инструментов для разработки на Java: компилятор javac, запускатор java, стандартная библиотека и утилиты.

    Какую версию Java выбрать для Paper

    Ориентируйтесь на требования вашей версии Paper. Обычно современные версии Paper требуют Java 17 или новее.

  • Сверяйтесь с документацией: Документация Paper
  • Держите одну и ту же версию Java для сборки и для запуска сервера, чтобы избежать неожиданных ошибок
  • Какой дистрибутив JDK ставить

    Подходящие варианты:

  • Eclipse Temurin (Adoptium) — популярный бесплатный вариант
  • Oracle JDK — официальный дистрибутив от Oracle
  • Практика для обучения и плагинов:

  • Ставьте Temurin JDK 17, если документация Paper не требует более новую версию
  • Проверка установки

    После установки проверьте в терминале:

    Ожидаемый смысл результата:

  • java -version показывает версию среды выполнения
  • javac -version показывает версию компилятора
  • Если версии разные, вы почти наверняка путаете несколько установок Java в системе.

    IDE: где писать код плагина

    IDE (среда разработки) помогает писать быстрее и безопаснее:

  • автодополнение по Bukkit/Paper API
  • подсказки типов, поиск usages, рефакторинги
  • запуск сборки Maven/Gradle из интерфейса
  • интеграция с Git
  • Рекомендуемый вариант

  • IntelliJ IDEA — самый распространённый выбор для Java-плагинов
  • Что важно включить в IDE

  • Подключённый Project SDK на нужную версию Java (например, 17)
  • Импорт проекта как Maven или Gradle проекта (не как “просто папка”)
  • Авто-импорт зависимостей, чтобы IDE видела Paper API
  • Мини-ориентир по структуре проекта

    Обычно Maven/Gradle проект для плагина выглядит так:

  • src/main/java — Java-код
  • src/main/resources — ресурсы (в будущем там будет plugin.yml, конфиги, сообщения)
  • файл сборки pom.xml или build.gradle
  • Maven и Gradle: зачем они нужны в плагинах

    Maven и Gradle — системы сборки. Они решают три ключевые задачи:

  • Скачивают зависимости (например, Paper API)
  • Компилируют код в .class
  • Собирают ваш плагин в .jar
  • Что выбрать новичку

    | Критерий | Maven | Gradle | |---|---|---| | Порог входа | Ниже, если вы любите “конфиг в XML” | Выше, но гибче | | Стиль | Декларативный pom.xml | Скриптовый build.gradle | | Часто в плагинах | Очень часто | Очень часто |

    Рекомендация:

  • Если вы хотите минимум магии и больше предсказуемости — берите Maven
  • Если хотите быстрее расти в сторону сложных сборок — Gradle
  • Дальше показаны оба варианта, но в одном проекте используйте только один.

    Maven: базовая настройка под Paper API

    Ключевая идея про Paper API как зависимость

    Paper API не должна “встраиваться” внутрь вашего .jar.

  • На сервере Paper уже есть нужные классы
  • Поэтому зависимость подключают как provided (Maven) или compileOnly (Gradle)
  • Пример pom.xml

    Версии Paper API зависят от версии сервера. Пример ниже показан как шаблон.

    Что здесь важно:

  • repositories добавляет репозиторий PaperMC
  • scope = provided означает: “нужно для компиляции, но не упаковывать в итоговый jar”
  • maven.compiler.source и maven.compiler.target фиксируют версию Java
  • Команды Maven, которые вы будете использовать постоянно

    | Действие | Команда | |---|---| | Сборка | mvn package | | Очистка и сборка заново | mvn clean package |

    Результат сборки обычно появится в target/.

    Официальные материалы:

  • Apache Maven
  • Gradle: базовая настройка под Paper API

    Пример build.gradle

    Что здесь важно:

  • compileOnly означает: зависимость нужна для компиляции, но не попадёт в jar
  • toolchain фиксирует версию Java, даже если у вас установлено несколько JDK
  • Команды Gradle

    | Действие | Команда | |---|---| | Сборка | ./gradlew build | | Очистка и сборка заново | ./gradlew clean build |

    Результат сборки обычно появится в build/libs/.

    Официальные материалы:

  • Gradle
  • Где брать правильную версию Paper API

    Версия paper-api должна соответствовать версии сервера, на котором вы тестируете.

    Рекомендации:

  • Выберите версию сервера Paper, которую будете запускать локально
  • Под эту же версию укажите paper-api в Maven/Gradle
  • Если сомневаетесь, начните с одной конкретной версии Minecraft и не меняйте её в середине обучения
  • Git: контроль версий для плагина

    Git нужен, чтобы:

  • сохранять историю изменений
  • откатываться к рабочей версии, если вы “сломали” проект
  • экспериментировать в отдельных ветках
  • удобно показывать код другим и получать помощь
  • Официальный сайт:

  • Git
  • Базовый рабочий процесс

  • git init — создать репозиторий в папке проекта
  • git add . — добавить файлы в индекс
  • git commit -m "init" — сделать коммит
  • дальше делать маленькие коммиты после каждого логического шага
  • Что нельзя коммитить

    Не добавляйте в репозиторий:

  • папки сборки target/ или build/
  • файлы IDE (.idea/, *.iml)
  • локальные настройки и кеши
  • Пример .gitignore для Java-проекта

    Ветки и теги

    Полезная минимальная схема:

  • main — стабильная ветка
  • feature/... — ветки под отдельные фичи
  • теги v1.0.0, v1.1.0 — отметки релизов, которые вы выдаёте на сервер
  • Мини-чеклист перед следующей статьёй

    Перед тем как переходить к созданию каркаса плагина, у вас должно быть готово:

  • JDK нужной версии установлена и проверена через java -version
  • IDE видит проект как Maven/Gradle и подтянула зависимости
  • Проект собирается в .jar одной командой
  • Git-репозиторий создан, есть первый коммит, настроен .gitignore
  • В следующей статье мы начнём собирать настоящий плагин: разберём жизненный цикл onEnable/onDisable, структуру проекта и первые подключаемые компоненты.

    3. Основы Spigot/Paper API: структура плагина и команды

    Основы Spigot/Paper API: структура плагина и команды

    Что вы уже умеете и что начинаем делать

    В прошлых статьях вы:

  • разобрали базу Java (ООП, коллекции, исключения)
  • настроили JDK, IDE, Maven/Gradle и Git
  • Теперь переходим к первому практическому взаимодействию с сервером: каркас плагина и команды. Команды удобны тем, что их легко тестировать: вы вводите /команда и сразу видите результат.

    > Важно: почти весь Bukkit/Paper API нужно вызывать из главного потока сервера. Для команд это обычно выполняется автоматически, потому что обработка команды идёт в основном потоке.

    Что такое Spigot/Paper API простыми словами

    API (Application Programming Interface) в контексте Paper — это набор классов и интерфейсов, через которые ваш плагин общается с сервером.

    Сервер:

  • загружает ваш .jar из папки plugins/
  • создаёт экземпляр главного класса плагина
  • вызывает методы жизненного цикла
  • вызывает ваш код, когда игрок вводит команду
  • !Схема показывает, кто и когда вызывает методы вашего плагина

    Полезные ссылки по API:

  • Документация Paper
  • Javadoc Spigot API
  • Минимальная структура проекта плагина

    Типичная структура Maven/Gradle проекта:

  • src/main/java — Java-код
  • src/main/resources — ресурсы, которые попадут в .jar
  • В src/main/resources почти всегда лежит plugin.yml — файл, по которому сервер понимает, что это за плагин и как его загружать.

    plugin.yml: что это и почему без него плагин не запустится

    plugin.yml лежит в корне ресурсов: src/main/resources/plugin.yml.

    Минимальный пример:

    Ключи plugin.yml

    | Ключ | Что означает | Типичная ошибка | |---|---|---| | name | имя плагина на сервере | меняют имя и забывают, что оно используется в логах и списках | | version | версия вашего плагина | оставляют пустым или не обновляют | | main | полный путь к главному классу | опечатка в пакете или имени класса | | api-version | версия API (в стиле Minecraft версии) | не указывают, получают предупреждения совместимости | | commands | список команд, которые “существуют” | добавляют команду в код, но не описывают здесь |

    Документация по plugin.yml:

  • Статья SpigotMC про plugin.yml
  • Главный класс плагина и жизненный цикл

    Главный класс — это класс, указанный в main: в plugin.yml. Он должен:

  • наследоваться от JavaPlugin
  • быть доступным серверу внутри вашего .jar
  • Пример:

    Когда вызываются методы

  • onEnable() вызывается при включении плагина (обычно при запуске сервера или при перезагрузке)
  • onDisable() вызывается при выключении (остановка сервера или отключение плагина)
  • Практические правила:

  • В onEnable() регистрируйте команды, слушатели событий, загружайте конфиги
  • В onDisable() сохраняйте данные и освобождайте ресурсы
  • Команды: как они устроены

    Команда состоит из двух частей:

  • Объявление команды в plugin.yml, чтобы сервер знал, что команда существует
  • Обработчик команды в Java-коде, который выполнится при вводе команды
  • В Paper/Spigot команды может вводить не только игрок.

  • Player — игрок в игре
  • ConsoleCommandSender — консоль сервера
  • Поэтому в обработчиках команд почти всегда есть проверка: кто именно отправитель.

    Реализация простой команды /hello

    Шаг 1: добавьте команду в plugin.yml

    Шаг 2: создайте обработчик CommandExecutor

    Что важно понимать в сигнатуре:

  • sender — кто ввёл команду
  • label — имя, под которым команда была вызвана (полезно при алиасах)
  • args — аргументы после команды
  • boolean результат:

  • true означает: команда обработана, Paper не будет автоматически показывать usage
  • false означает: Paper покажет текст из usage: в plugin.yml
  • Шаг 3: зарегистрируйте обработчик в onEnable()

    Почему нужна проверка getCommand("hello") == null:

  • если вы забыли команду в plugin.yml
  • если вы ошиблись в имени (hello и hell0)
  • Без проверки вы получите NullPointerException при setExecutor.

    Команды с аргументами: мини-пример /heal <player>

    Покажем типичную валидацию аргументов.

    plugin.yml:

    Код:

    Ключевые идеи:

  • args.length проверяет количество аргументов
  • Bukkit.getPlayerExact(...) может вернуть null, поэтому нужна проверка
  • return false удобно использовать, когда вы хотите показать usage из plugin.yml
  • Права (permissions): как ограничить доступ к командам

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

    Объявление прав в plugin.yml

    default: op означает, что право есть у операторов сервера.

    Проверка права в коде

    Практика:

  • имя права обычно начинается с имени плагина: myplugin.*
  • проверку прав лучше делать в самом начале обработчика
  • Автодополнение аргументов (TabCompleter)

    Когда игрок нажимает Tab, сервер может попросить ваш плагин подсказать варианты.

    Шаблон:

    Регистрация рядом с setExecutor:

    Проверка плагина на сервере

    Чтобы протестировать плагин локально:

  • Соберите .jar через Maven или Gradle.
  • Скопируйте .jar в папку plugins/ вашего Paper-сервера.
  • Запустите сервер.
  • Проверьте, что плагин появился в /plugins.
  • Проверьте команду /hello.
  • Если сервер пишет ошибки:

  • сначала проверьте main: в plugin.yml
  • затем проверьте, что plugin.yml действительно попал в итоговый .jar
  • затем проверьте совпадение имён команд в plugin.yml и в getCommand("...")
  • Итоги

    Теперь у вас есть минимально рабочая основа, на которой строится почти любой плагин:

  • plugin.yml описывает плагин и команды
  • главный класс наследуется от JavaPlugin и использует onEnable/onDisable
  • команды реализуются через CommandExecutor и регистрируются через getCommand(...).setExecutor(...)
  • аргументы команд требуют явной проверки
  • права проверяются через hasPermission и могут быть объявлены в plugin.yml
  • таб-комплитер делает команды удобнее
  • В следующем материале логично перейти к событиям (listeners): как реагировать на действия игроков и мира так же надёжно, как мы сейчас реагируем на команды.

    4. События, задачи и работа с миром: игроки, блоки, сущности

    События, задачи и работа с миром: игроки, блоки, сущности

    Зачем нужны события и задачи после команд

    В предыдущей статье вы сделали каркас плагина и подключили команды. Команды хороши тем, что их легко тестировать, но в реальном плагине большую часть логики вы будете запускать не по /команда, а автоматически, когда что-то происходит на сервере.

    Для этого в Paper/Spigot есть два ключевых инструмента:

  • События (events): сервер сообщает вашему плагину, что произошло действие (игрок вошёл, блок сломан, сущность получила урон).
  • Задачи (scheduler): ваш плагин запускает код позже или регулярно (таймеры, кулдауны, периодическая проверка).
  • Эта статья даст вам практическую базу, чтобы:

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

  • Документация Paper
  • Javadoc Spigot API
  • !Схема показывает, как действие игрока превращается в событие и как плагин может повлиять на итог.

    События: как сервер “звонит” в ваш код

    Что такое событие

    Событие — это объект, который описывает факт, произошедший на сервере. Например:

  • PlayerJoinEvent — игрок зашёл
  • BlockBreakEvent — игрок сломал блок
  • EntityDamageEvent — сущность получила урон
  • Ваша задача — зарегистрировать слушателя (listener), и пометить методы обработчиков аннотацией @EventHandler.

    Listener: минимальный шаблон

    Создадим слушатель входа игрока.

    Регистрация слушателя в onEnable()

    Слушатели не работают, пока вы их не зарегистрируете.

    Практика:

  • Регистрируйте слушатели в onEnable()
  • Не храните “всё в одном слушателе”: удобнее разделять по смыслу (например, JoinListener, BlockProtectionListener, CombatListener)
  • Отмена событий: как запрещать действия

    Многие события можно отменить. Это означает: “сервер, не применяй стандартное действие”.

    Пример: запретить ломать алмазы

    Важно:

  • Проверяйте тип блока через block.getType()
  • Отмена через event.setCancelled(true)
  • Приоритеты и ignoreCancelled

    Иногда несколько плагинов реагируют на одно событие, и порядок важен.

    Приоритет обработчика

    У @EventHandler есть параметр priority. Чем выше приоритет, тем позже (обычно) будет вызван обработчик.

    Общая идея (упрощённо):

  • LOWEST и LOW — ранняя обработка
  • NORMAL — стандарт
  • HIGH и HIGHEST — поздняя обработка
  • MONITOR — наблюдение (обычно без изменения события)
  • ignoreCancelled

    Если событие уже отменили до вас, а ваш обработчик не должен запускаться, используйте ignoreCancelled = true.

    Практика:

  • Если вы “начисляете награды” за действие, ставьте ignoreCancelled = true, чтобы не награждать за запрещённое действие
  • Если вы “запрещаете” действие, приоритет часто ставят HIGH или HIGHEST
  • Задачи (Scheduler): запуск кода позже или по таймеру

    Зачем нужен scheduler

    Задачи нужны, когда вы хотите:

  • сделать задержку (например, телепорт через 5 секунд)
  • повторять действие (например, каждую секунду показывать информацию)
  • выполнить тяжёлую работу асинхронно (например, расчёты или запросы к внешнему API)
  • Ключевая единица времени в scheduler — тики.

  • 20 тиков = примерно 1 секунда
  • Задача “через N тиков”

    Где:

  • this — ваш плагин (JavaPlugin)
  • лямбда — код, который выполнится
  • 20L * 5 — задержка в тиках
  • Повторяющаяся задача

    Где:

  • 0L — стартовая задержка
  • 20L — период повторения
  • Отмена задачи

    Если вы хотите уметь остановить таймер, сохраните taskId.

    !Диаграмма помогает запомнить, что API мира нельзя трогать из async.

    Синхронно и асинхронно: главное правило безопасности

    Большая часть работы с миром (блоки, сущности, телепорты, инвентари) должна выполняться в главном потоке сервера.

    Практическое правило:

  • Если код трогает Player, World, Entity, Block, почти всегда выполняйте его синхронно
  • Асинхронные задачи полезны для:

  • HTTP-запросов к сайтам
  • работы с внешними сервисами
  • тяжёлых вычислений
  • Типичный безопасный шаблон:

  • Асинхронно считаем/получаем данные
  • Синхронно применяем результат к миру
  • Работа с игроками

    Получить игрока и его идентификатор

    Игрока часто идентифицируют по UUID.

    Практика:

  • Храните данные по UUID, а не по name, потому что ник можно менять
  • Игрок онлайн или нет

    Если вы ищете игрока по нику, вам могут вернуть null.

    Телепорт

    Локации и мир

    Location: из чего состоит

    Location описывает точку в мире:

  • мир (World)
  • координаты x, y, z
  • поворот: yaw, pitch
  • Пример создания локации:

    Практика:

  • Bukkit.getWorld(...) может вернуть null, если мира нет или он ещё не загружен
  • Координаты с .5 часто используют, чтобы поставить игрока в центр блока
  • Работа с блоками

    Получить блок по локации

    Проверить тип блока и заменить

    Практика:

  • Изменение блоков — это “работа с миром”, выполняйте её синхронно
  • Всегда думайте о последствиях: массовая замена блоков в цикле может лагать
  • Пример события: “снежный след” под игроком

    Ограничение, о котором важно знать:

  • PlayerMoveEvent вызывается очень часто
  • Практика оптимизации:

  • Делайте быстрые проверки и минимальные действия
  • Если логика тяжёлая, переносите её в повторяющуюся задачу раз в несколько тиков
  • Работа с сущностями (Entity)

    Что такое сущность

    Сущность — это объект в мире: игрок, моб, предмет на земле, стрелы.

  • Entity — общий тип
  • LivingEntity — живые (мобы и игроки)
  • Player — игрок
  • Пример: спавн моба рядом с игроком

    Пример: обработка урона

    Практика:

  • event.getEntity() возвращает Entity, поэтому тип проверяют через instanceof
  • Урон часто меняют через event.setDamage(...) или отменяют через event.setCancelled(true)
  • Типовой мини-плагин: “автохил раз в 10 секунд”

    Это пример, где вместе используются и игроки, и задачи.

    Идея:

  • Каждые 10 секунд пройтись по онлайн-игрокам
  • Если у игрока здоровье меньше максимума — немного подлечить
  • Почему это хороший учебный пример:

  • Все действия синхронны
  • Логика ограничена по частоте (раз в 10 секунд)
  • Используется безопасная проверка Math.min(...), чтобы не превысить максимум
  • Частые ошибки новичков

  • Не зарегистрировали listener через registerEvents(...)
  • Пытаются менять мир из async-задачи
  • Забывают проверки на null (например, Bukkit.getWorld(...), Bukkit.getPlayerExact(...))
  • Делают тяжёлую логику в частых событиях (например, в PlayerMoveEvent)
  • Смешивают ответственность: весь код в MyPlugin вместо отдельных классов
  • Итоги

    Теперь у вас есть практический фундамент для “живого” плагина:

  • События: Listener, @EventHandler, регистрация в onEnable()
  • Отмена событий: event.setCancelled(true) для запрета действий
  • Приоритеты и ignoreCancelled для корректного взаимодействия обработчиков
  • Задачи: runTaskLater, runTaskTimer, отмена задач
  • Работа с миром: игроки, блоки, сущности, Location, базовые операции
  • Следующий логичный шаг — сохранить состояние плагина между перезапусками: конфиги, YAML-файлы, простая база данных, а также аккуратная архитектура сервисов, чтобы события и команды не превращались в хаос.

    5. Конфиги, хранение данных и релиз: YAML, базы, сборка, отладка

    Конфиги, хранение данных и релиз: YAML, базы, сборка, отладка

    Как эта тема связана с предыдущими статьями

    Ранее вы научились:

  • Писать команды и регистрировать их через plugin.yml
  • Реагировать на события и запускать задачи через scheduler
  • Хранить состояние в коллекциях (Map<UUID, ...>) и понимать ограничения потоков
  • Теперь мы закрываем практическую проблему любого реального плагина: как сделать так, чтобы настройки и данные не терялись при перезапуске сервера, а сам плагин было удобно собирать, проверять и выпускать.

    Эта статья охватывает:

  • Конфиги в YAML: config.yml и дополнительные файлы
  • Хранение данных игроков: от YAML до базы
  • Сборку релизного .jar и типичные ошибки
  • Отладку: логи, stack trace, воспроизводимость
  • !Схема показывает, где вы читаете настройки, где держите кеш и когда сохраняете данные

    YAML-конфиг плагина: config.yml

    Что такое config.yml

    config.yml обычно лежит в src/main/resources/config.yml и попадает внутрь вашего .jar. При первом запуске плагина этот файл копируется в папку плагина на сервере: plugins/ИмяПлагина/config.yml.

    Ключевая идея:

  • Внутри .jar лежат дефолтные настройки
  • В папке plugins/... лежат реальные настройки сервера, которые админ может менять
  • Документация по конфигам Bukkit/Spigot:

  • Spigot API: FileConfiguration
  • Spigot API: YamlConfiguration
  • Минимальный пример config.yml

    Загрузка дефолтного конфига

    В onEnable() обычно делают так:

    Что важно:

  • saveDefaultConfig() создаст файл на диске, только если его ещё нет
  • getConfig() возвращает объект FileConfiguration
  • Для строк, чисел и булевых значений используйте варианты методов с дефолтом, чтобы не ловить null
  • Чтение значений: строки, числа, списки

    Практика:

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

    Если вы делаете команду типа /myplugin reload, используйте:

    Типичная ошибка: перезагрузить config.yml, но не обновить поля, которые вы заранее прочитали. Решение: держать метод типа loadSettings().

    Дополнительные YAML-файлы: не только config.yml

    config.yml подходит для настроек, но данные игроков часто удобнее хранить отдельно.

    Где хранить файлы

    У любого JavaPlugin есть папка данных:

    Если вы создаёте свои файлы, убедитесь, что папка существует:

    Пример: playerdata.yml через YamlConfiguration

    Практика:

  • Данные игроков почти всегда ключуйте по UUID, а не по имени
  • Сохранять на диск при каждом чихе дорого; лучше кешировать и сохранять периодически или при onDisable()
  • Стратегии хранения данных: YAML vs база

    Выбор зависит от объёма данных, частоты изменений и требований к надёжности.

    | Подход | Плюсы | Минусы | Когда использовать | |---|---|---|---| | В памяти (Map) и сохранение в YAML | Просто, минимум зависимостей | YAML медленнее на большие объёмы, риск лагов при частом сохранении | Учебные плагины, небольшие серверы | | YAML файл на игрока (players/<uuid>.yml) | Файлы меньше, проще частично загружать | Много файлов, нужен порядок чтения/записи | Средний объём данных | | SQLite (встроенная база в файл) | Быстрые запросы, меньше лагов при правильном использовании | Нужно JDBC и аккуратность с потоками | Статистика, экономики, лидерборды | | MySQL/PostgreSQL | Можно разделить данные между серверами, надёжно | Сложнее настройка, внешняя зависимость | Сети серверов, большие проекты |

    Почему часто начинают с YAML

    YAML помогает быстро получить результат и понять архитектуру:

  • Команда меняет состояние
  • События меняют состояние
  • Нужно сохранить состояние и восстановить
  • Когда вы это освоили, переход на SQLite становится более понятным: вместо YamlConfiguration вы делаете INSERT/UPDATE.

    Архитектурная практика: кеш + периодическое сохранение

    Самый частый шаблон для небольших данных:

  • В памяти: Map<UUID, PlayerData>
  • При входе: загрузить из файла в кеш
  • В процессе игры: работать только с кешем
  • Раз в N секунд: сохранять изменённые записи
  • При выходе и при onDisable(): сохранить
  • Мини-пример структуры:

    Идея dirty:

  • Если игроку ничего не меняли, не надо лишний раз сохранять
  • Важно про потоки:

  • Если вы сохраняете YAML, делайте запись аккуратно: файл нельзя одновременно писать из двух мест
  • Если вы используете базу, запросы обычно выполняют асинхронно, а применение результатов к Bukkit API делают синхронно
  • SQLite и JDBC: базовый ориентир

    Если вы решите перейти на SQLite, вам понадобится:

  • Драйвер JDBC для SQLite
  • Инициализация таблиц при старте
  • Аккуратная работа с соединениями и потоками
  • Полезные материалы:

  • Документация SQLite
  • JDBC Tutorial (Oracle)
  • Практический комментарий для Paper:

  • Если вы добавляете JDBC-драйвер как зависимость, его обычно нужно включить в jar (shading), иначе на сервере его не будет
  • Сборка и релиз: как получить правильный .jar

    Что должно попасть в итоговый .jar

    Обычно в релизном .jar должны быть:

  • Ваши .class
  • Ресурсы: plugin.yml, config.yml, файлы локализации и т. п.
  • И обычно не должно быть:

  • Paper API (paper-api) внутри jar
  • Проверка:

  • Откройте .jar как архив и убедитесь, что plugin.yml лежит в корне
  • Maven: базовая сборка

    Команда сборки:

    Результат:

  • target/имя-версия.jar
  • Gradle: базовая сборка

    Команда сборки:

    Результат:

  • build/libs/имя-версия.jar
  • Shading зависимостей: когда это нужно

    Shading нужен, когда вы используете библиотеку, которой нет на сервере.

    Типичный пример:

  • JDBC драйвер SQLite
  • JSON библиотека (если вы не хотите использовать встроенные возможности)
  • Для Maven это обычно делают через maven-shade-plugin.

    Официальная документация:

  • Maven Shade Plugin
  • Ключевая идея:

  • Paper API остаётся provided
  • Ваши сторонние библиотеки шейдятся, чтобы плагин работал без ручной установки зависимостей на сервер
  • Версионирование и выпуск

    Версия плагина

    Версия указывается минимум в двух местах:

  • plugin.yml в version:
  • системе сборки (pom.xml или build.gradle)
  • Практика:

  • Держите эти версии согласованными
  • Делайте релиз, когда набор изменений логически завершён
  • Что проверять перед релизом

    Короткий чеклист:

  • Плагин стартует без ошибок на чистом сервере
  • Команды и события работают
  • Конфиг создаётся через saveDefaultConfig()
  • Данные сохраняются и загружаются после перезапуска
  • В логах нет спама и лишних исключений
  • Отладка: как быстрее находить ошибки

    Логи и getLogger()

    Вместо System.out.println используйте логгер плагина:

    Плюсы:

  • В логах видно, какой плагин пишет сообщение
  • Уровни важности упрощают чтение
  • Как читать stack trace

    Если сервер пишет исключение, важно смотреть:

  • Первая строка: тип ошибки (NullPointerException, IllegalArgumentException)
  • Строки со ссылкой на ваш пакет (com.example...) и номер строки
  • Практика:

  • Почти всегда причина находится в первых строках stack trace и в первой строке вашего кода, которая там упоминается
  • Типичные причины ошибок в плагинах

  • getCommand("...") == null, потому что команда не описана в plugin.yml
  • NullPointerException из-за Bukkit.getWorld(...) == null или getConfig().getString(...) без дефолта
  • Изменение мира из async-задачи
  • Сохранение файла слишком часто и в неподходящий момент
  • Минимальная диагностика входных данных

    Команды и конфиги всегда надо валидировать.

    Пример безопасного чтения чисел:

    Итоги

    В этом материале вы научились превращать учебный плагин в реально используемый:

  • Подключать и читать config.yml, делать reload и дефолтные значения
  • Создавать дополнительные YAML-файлы и хранить данные по UUID
  • Выбирать стратегию хранения: YAML или база, и понимать компромиссы
  • Собирать релизный .jar и понимать, когда нужен shading зависимостей
  • Быстрее отлаживать ошибки по логам и stack trace
  • Следующий логичный шаг после этой статьи: привести код к более устойчивой структуре (сервисы, слои доступа к данным, аккуратные зависимости), чтобы плагин рос без превращения в один большой класс.