Event listeners

Listen for and handle server events in C++.

Endstone raises an event whenever something happens in the game - a player joins, a block breaks, a message is sent. A plugin reacts by registering a listener for the events it cares about. The scenarios below go from simply observing an event to changing or vetoing what the server does.

Write a handler

Add a method that takes the event by reference:

include/my_plugin.h
#include <endstone/endstone.hpp>

namespace es = endstone;

class MyPlugin : public es::Plugin {
public:
    void onPlayerJoin(es::PlayerJoinEvent &event)
    {
        getServer().broadcastMessage(
            es::ColorFormat::Yellow + "{} has joined the server",
            event.getPlayer().getName());
    }
};

Register the listener

Handlers only fire once registered. Call registerEvent in onEnable, pointing it at your handler:

include/my_plugin.h
    void onEnable() override
    {
        registerEvent(&MyPlugin::onPlayerJoin, *this);
    }

Use one registerEvent call per handler, and add as many as you need to listen for more events.

Cancel an event

Many events are cancellable - calling event.setCancelled(true) tells the server not to do the thing it was about to. Here, only operators may break blocks:

include/my_plugin.h
    void onBlockBreak(es::BlockBreakEvent &event)
    {
        if (!event.getPlayer().isOp()) {
            event.setCancelled(true);
            event.getPlayer().sendMessage("You can't break blocks here.");
        }
    }

Change an event

Some events let you rewrite their data before the server acts on it. PlayerChatEvent exposes the outgoing message through getMessage and setMessage:

include/my_plugin.h
    void onPlayerChat(es::PlayerChatEvent &event)
    {
        event.setMessage("[member] " + event.getMessage());
    }

Run at a set priority

When several listeners handle the same event, they run in order: LowestLowNormal (the default) → HighHighestMonitor. Pass a priority as the third argument to registerEvent to control where yours falls. Monitor runs last and is meant for observing the final outcome, not changing it:

include/my_plugin.h
    void onEnable() override
    {
        registerEvent(&MyPlugin::onBlockBreak, *this, es::EventPriority::Monitor);
    }

    void onBlockBreak(es::BlockBreakEvent &event)
    {
        if (!event.isCancelled()) {
            getLogger().info("{} broke a block", event.getPlayer().getName());
        }
    }

Skip already-cancelled events

By default a handler still runs even if an earlier listener cancelled the event. Pass true as the fourth argument (ignore_cancelled) to bow out when that's already happened:

include/my_plugin.h
    void onEnable() override
    {
        // priority Normal, ignore_cancelled = true
        registerEvent(&MyPlugin::onBlockBreak, *this, es::EventPriority::Normal, true);
    }

On this page