Flectone LogoFlectonePulse

Система событий

Аннотация @Pulse, интерфейс PulseListener, приоритеты обработки, отмена и изменение неизменяемых событий

События это главный способ вмешаться в работу FlectonePulse. Они срабатывают на всех этапах, от входа игрока до сборки каждого сообщения

КомпонентЗачем нужен
EventБазовый интерфейс всех событий, умеет отменяться
EventDispatcherПрогоняет событие через слушателей по приоритету
ListenerRegistryРегистрирует и удаляет слушателей
@PulseПомечает метод обработчиком
PulseListenerПомечает класс, в котором лежат обработчики

События неизменяемы

Каждое событие это record. Поменять поле на месте нельзя, методы with* отдают новую копию, и эту копию обработчик обязан вернуть через return

// не работает, копия создана, но никуда не передана
@Pulse
public void onFormatting(MessageFormattingEvent event) {
    event.withContext(event.context().withMessage("новый текст"));
}

// работает, копия уходит диспетчеру
@Pulse
public Event onFormatting(MessageFormattingEvent event) {
    return event.withContext(event.context().withMessage("новый текст"));
}
Внимание

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


Свой слушатель

Реализуй интерфейс PulseListener

Объяви публичные методы с аннотацией @Pulse и одним параметром, типом нужного события

Зарегистрируй слушателя через ListenerRegistry

import net.flectone.pulse.annotation.Pulse;
import net.flectone.pulse.listener.PulseListener;
import net.flectone.pulse.model.event.Event;
import net.flectone.pulse.model.event.message.MessageFormattingEvent;
import net.flectone.pulse.model.event.message.context.MessageContext;
import net.flectone.pulse.model.event.player.PlayerJoinEvent;

public class MyCustomListener implements PulseListener {

    // меняем сообщение во время форматирования, поэтому нужен return
    @Pulse(priority = Event.Priority.NORMAL)
    public Event onMessageFormatting(MessageFormattingEvent event) {
        MessageContext messageContext = event.context();

        String modifiedMessage = messageContext.message() + " [Modified]";
        return event.withContext(messageContext.withMessage(modifiedMessage));
    }

    // с ignoreCancelled метод вызовется, даже если событие уже отменено
    @Pulse(priority = Event.Priority.LOWEST, ignoreCancelled = true)
    public void onPlayerJoin(PlayerJoinEvent event) {
        String playerIp = event.player().ip();
    }

}
Предупреждение

Метод должен быть публичным и принимать ровно один параметр, наследующий Event. Иначе при регистрации прилетит ListenerRegistrationException


Регистрация

Слушателей регистрирует ListenerRegistry

import net.flectone.pulse.platform.registry.ListenerRegistry;

ListenerRegistry listenerRegistry = flectonePulse.get(ListenerRegistry.class);

// для своих плагинов, переживёт /flectonepulse reload
listenerRegistry.registerPermanent(new MyCustomListener());
МетодЧто делает
registerPermanent(PulseListener)Регистрирует слушателя и восстанавливает его после перезагрузки
register(PulseListener)Регистрирует готовый экземпляр до ближайшей перезагрузки
register(Class<?>)Создаёт слушателя через Guice и регистрирует его
register(Class<? extends Event>, Priority, UnaryOperator<Event>)Регистрирует одиночный обработчик без отдельного класса
unregisterAll()Удаляет всех слушателей, включая постоянных
getPulseListeners(Class<? extends Event>)Отдаёт обработчики события, разложенные по приоритетам
Внимание

При /flectonepulse reload вызывается unregisterAll(), после чего заново регистрируются стандартные и постоянные слушатели. Если ты использовал register(), твой слушатель после перезагрузки пропадёт

Обработчик без класса

Ради одного простого обработчика отдельный класс заводить не обязательно

import net.flectone.pulse.model.event.Event;
import net.flectone.pulse.model.event.player.PlayerQuitEvent;

listenerRegistry.register(PlayerQuitEvent.class, Event.Priority.MONITOR, event -> {
    PlayerQuitEvent quitEvent = (PlayerQuitEvent) event;
    getLogger().info(quitEvent.player().name() + " вышел");
    return event;
});

Приоритеты

EventDispatcher вызывает обработчики по возрастанию приоритета

ПриоритетПорядокДля чего подходит
LOWESTПервыйРанняя отмена события или подготовка данных
LOWВторойПроверка данных
NORMALТретийОбычная обработка, стоит по умолчанию
HIGHЧетвёртыйОбработка после основных изменений
HIGHESTПятыйФинальные правки
MONITORПоследнийТолько наблюдение и логирование, без изменений

Внутри одного приоритета обработчики идут в порядке регистрации


Отмена события

Отменённое событие останавливает действие, ради которого его вызвали

@Pulse(priority = Event.Priority.LOWEST)
public Event onPreLogin(PlayerPreLoginEvent event) {
    if (event.player().ip() != null && event.player().ip().startsWith("10.")) {
        return event.withAllowed(false)
                .withKickReason(Component.text("Вход из локальной сети запрещён"));
    }

    return event;
}

Узнать, отменил ли событие кто то другой, можно через cancelled()

@Pulse(priority = Event.Priority.MONITOR, ignoreCancelled = true)
public void onSend(MessageSendEvent event) {
    if (event.cancelled()) {
        getLogger().info("Сообщение отменил другой слушатель");
    }
}

ignoreCancelled

ЗначениеПоведение
falseОбработчик не вызовется, если событие уже отменено. Стоит по умолчанию
trueОбработчик вызовется в любом случае

Ручная отправка

Прогнать событие через слушателей можно самому, для этого есть EventDispatcher

import net.flectone.pulse.dispatcher.EventDispatcher;

EventDispatcher eventDispatcher = flectonePulse.get(EventDispatcher.class);

MessageReceiveEvent result = eventDispatcher.dispatch(
        new MessageReceiveEvent(fPlayer, Component.text("Привет"), false)
);

if (!result.cancelled()) {
    // никто не отменил, можно отправлять
}

dispatch() отдаёт событие в том виде, в котором его оставили слушатели


Свои события

Своё событие пишется как record, реализующий Event. Аннотация Lombok @With сама сгенерирует методы копирования

import lombok.With;
import net.flectone.pulse.model.entity.FPlayer;
import net.flectone.pulse.model.event.Event;

@With
public record MyCustomEvent(
        boolean cancelled,
        FPlayer player,
        String data
) implements Event {

    public MyCustomEvent(FPlayer player, String data) {
        this(false, player, data);
    }

}

Отправляется оно тем же EventDispatcher, а слушатели пишутся точно так же, как для встроенных событий

Примечание

События обрабатываются синхронно и по порядку приоритета, поэтому долгие операции выноси в планировщик. Ошибка внутри обработчика не роняет цепочку, FlectonePulse запишет предупреждение в лог и продолжит с исходным событием

Последнее изменение 11 августа 2026 г.
Редактировать на GitHub

На этой странице