Разработка Java-серверов на Paper · 6 мин чтения

Слушатели Paper: засчитывайте успешное действие один раз

Начните со слушателя добычи камня, который сообщает о разрешённом действии и пропускает отменённое. Полный класс ниже регистрирует событие, фильтрует мир и режим; затем его можно проверить рядом с защитой региона.

Редакция LonelyCraft · Обновлено

Схема: Фильтр → Событие → Сервис
Ранние фильтры, учёт отмены и общий сервис одноразовой цели. Авторская схема LonelyCraft.

Подготовьте пример и выберите событие

Слушатель ниже работает на Paper 1.21.1 с JDK 21. Подготовьте проект по гайду первого плагина, затем замените src/main/java/ru/example/trailnotes/TrailNotesPlugin.java целиком: этот класс одновременно запускает плагин и принимает события.

Задача примера: вывести подсказку игроку только при разрушении STONE в Survival в мире world. Имя мира замените фактическим, если ваш главный мир называется иначе. Мы не выдаём валюту и не называем камень природным: один BlockBreakEvent не сообщает историю установки блока. API регистрации и приоритеты описаны в Paper Listeners.

Замените главный класс целиком

Сохраните код в src/main/java/ru/example/trailnotes/TrailNotesPlugin.java. Manifest остаётся из первого проекта. Приоритет MONITOR наблюдает результат, ignoreCancelled=true пропускает отмену, а обработчик не изменяет событие.

package ru.example.trailnotes;

import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.plugin.java.JavaPlugin;

public final class TrailNotesPlugin extends JavaPlugin implements Listener {
    @Override
    public void onEnable() {
        getServer().getPluginManager().registerEvents(this, this);
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onBreak(BlockBreakEvent event) {
        if (!event.getBlock().getWorld().getName().equals("world")) return;
        if (event.getPlayer().getGameMode() != GameMode.SURVIVAL) return;
        if (event.getBlock().getType() != Material.STONE) return;
        event.getPlayer().sendMessage("Разрешённое разрушение камня учтено.");
    }
}

Соберите и воспроизведите четыре случая

Соберите через ./gradlew clean build в Linux/macOS или ./gradlew.bat clean build в Windows. Замените тестовый JAR на остановленном Paper, затем запустите его. Входите игроком без OP; для смены режима используйте консоль тестового сервера.

  1. В Survival в world разрушьте камень: должна появиться одна подсказка. Разрушьте землю: подсказки нет.
  2. Перейдите в Creative и разрушьте камень: фильтр режима оставляет чат пустым.
  3. В защищённом регионе без права строительства попробуйте камень. Блок остаётся, сообщение о засчитанном разрушении не появляется.
  4. Поставьте камень сами и сломайте: этот учебный обработчик также сообщает о нём. Для наград за природную руду требуется дополнительное происхождение или ограниченное правило задания.

Найдите причину отсутствующего сообщения

Сначала проверьте включение TrailNotes в logs/latest.log и вызов registerEvents в onEnable. Затем по очереди сравните фактическое имя мира, GameMode и Material. Добавляйте диагностический лог перед конкретным фильтром в тестовой копии, а не SQL-запрос в каждое событие.

Если другой плагин запрещает действие, не снимайте его отмену ради подсказки. Для настоящего задания обработчик передаёт подтверждённый факт сервису прогресса, который проверяет уникальность и хранит результат. Повторяемое сообщение примера не является защитой от повторной награды.

Начните с одного проверяемого события

Класс слушателя реализует Listener, метод получает событие и помечается EventHandler. Экземпляр регистрируется через PluginManager при включении плагина. Наличие класса в JAR само по себе не регистрирует обработку. Для первого теста выберите одно понятное действие и короткую запись в журнал, чтобы отделить отсутствие события от ошибки бизнес-логики.

Затем добавьте ранние фильтры: нужный мир, режим, тип объекта и участие игрока. Например, награда за природную руду не должна начинать SQL-запрос при каждом разрушении любой земли. Фильтры уменьшают нагрузку и одновременно показывают правила механики. Их порядок удобнее читать от самой дешёвой и общей проверки к конкретной.

Отмена и приоритет

ignoreCancelled=true пропускает обработку уже отменённого события. Приоритеты определяют порядок вызова, а MONITOR предназначен для наблюдения итогового состояния без его изменения. Если прогресс должен отражать успешное действие, выбирайте момент и API события, в которых успех действительно подтверждён. Обработка раннего намерения не всегда равна завершению результата.

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

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

Событие превратите в факт сервиса

Слушатель собирает необходимые простые данные и вызывает метод вроде recordResource(UUID, material, origin). Сервис проверяет активную цель, период и идемпотентность. Это позволяет отдельно тестировать прогресс, не создавая искусственный Bukkit-event для каждого расчёта. Название метода должно отражать подтверждённое действие, а не устройство интерфейса.

Для одноразовых целей нужен устойчивый ключ: игрок, правило, объект или период. Проверка HashSet в памяти ускоряет повторные события, но не сохраняется после рестарта. Уникальность в хранилище защищает от двух быстрых обработчиков и повторного входа. Награда должна следовать факту разблокировки, а не числу попыток вызвать событие.

Уникальные ограничения и транзакция подтверждают одноразовую цель при повторных событиях. Устойчивый учёт прогресса.

Проверьте руки и способы взаимодействия

Некоторые события взаимодействия связаны с основной и второй рукой. Если действие должно засчитываться один раз, задайте допустимую руку и проверьте фактическую цепочку событий. Сравнивайте этот сценарий на реальном клиенте. Не предполагайте, что каждый EventHandler вызывается ровно один раз на физический щелчок.

Для GUI недостаточно слушать один тип клика: перенос, shift-click, цифровые клавиши и drag имеют разные события и действия. Для крафта надо считать фактическое полученное количество, а не один открытый рецепт. Для убийства уточните источник подтверждённого последнего удара. Каждый вид прогресса требует своей формулировки успешного факта.

  1. Нужное действие засчитывается в разрешённом мире.
  2. Такое же действие вне мира или режима игнорируется.
  3. Отменённое исходное действие не начисляет награду.
  4. Вторая рука и быстрый повтор не удваивают результат.
  5. Два конкурирующих события сохраняют одну разблокировку.
  6. После рестарта прежняя одноразовая цель не выдаётся повторно.

Происхождение блока не видно из Material

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

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

Диагностика без постоянного шума

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

При обновлении зависимости проверьте публичное событие и его назначение заново. Появление похожего класса не гарантирует прежний момент вызова. Интеграция мини-игры должна сообщать завершённый раунд или найденную цель; открытие меню и вход в арену — другие факты и не должны подменять успех.

Частые вопросы

Зачем registerEvents, если метод уже имеет EventHandler?

Аннотация описывает обработчик, но экземпляр слушателя нужно зарегистрировать в PluginManager. Иначе сервер не вызовет его для события.

Что делает ignoreCancelled?

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

MONITOR подходит для отмены действия?

Этот приоритет предназначен для наблюдения результата без изменения события. Политику отмены реализуют на подходящем рабочем приоритете.

Можно ли определить природный блок по Material?

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

Просмотры: … · Один просмотр с браузера за день

Было полезно?Общий счёт: …

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

Источники и документация

Minecraftразработка плагиновPaper

Читайте дальше