Подготовьте пример и выберите событие
Слушатель ниже работает на 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; для смены режима используйте консоль тестового сервера.
- В Survival в
worldразрушьте камень: должна появиться одна подсказка. Разрушьте землю: подсказки нет. - Перейдите в Creative и разрушьте камень: фильтр режима оставляет чат пустым.
- В защищённом регионе без права строительства попробуйте камень. Блок остаётся, сообщение о засчитанном разрушении не появляется.
- Поставьте камень сами и сломайте: этот учебный обработчик также сообщает о нём. Для наград за природную руду требуется дополнительное происхождение или ограниченное правило задания.
Найдите причину отсутствующего сообщения
Сначала проверьте включение 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 имеют разные события и действия. Для крафта надо считать фактическое полученное количество, а не один открытый рецепт. Для убийства уточните источник подтверждённого последнего удара. Каждый вид прогресса требует своей формулировки успешного факта.
- Нужное действие засчитывается в разрешённом мире.
- Такое же действие вне мира или режима игнорируется.
- Отменённое исходное действие не начисляет награду.
- Вторая рука и быстрый повтор не удваивают результат.
- Два конкурирующих события сохраняют одну разблокировку.
- После рестарта прежняя одноразовая цель не выдаётся повторно.
Происхождение блока не видно из Material
Если награда предназначена для добычи, игрок может поставить купленный блок и сломать его. Тип материала одинаков, но игровая ценность действия отличается. Нужна явная политика: допускается любой блок либо хранится происхождение. При втором варианте учитывайте постановку, поршневое перемещение и сохранение отметок вместе с чанком.
У такой системы есть предел: блоки, поставленные до её установки, могут не иметь ретроспективной метки. Опишите это ограничение и не обещайте идеальную историю без данных. Для искусственных мобов также нужна подходящая модель источника, если фермы и спавнеры не должны давать соответствующий прогресс.
Диагностика без постоянного шума
Добавьте временный режим отладки с UUID, правилом и короткой причиной пропуска. Не пишите полный поток движения всех игроков в большой журнал. Для повторяемого случая достаточно одного тестового аккаунта и нескольких событий. После подтверждения причины отключите подробный режим, сохранив необходимые ошибки и метрики работы.
При обновлении зависимости проверьте публичное событие и его назначение заново. Появление похожего класса не гарантирует прежний момент вызова. Интеграция мини-игры должна сообщать завершённый раунд или найденную цель; открытие меню и вход в арену — другие факты и не должны подменять успех.



