PocketMine-MP’s event system allows plugins to listen and react to server events like player joins, block breaks, and entity spawns. The system is synchronous and runs on the main thread during the tick cycle.
use pocketmine\plugin\PluginBase;class MyPlugin extends PluginBase { protected function onEnable(): void { // Register listener instance $this->getServer()->getPluginManager()->registerEvents( new MyListener($this), $this ); // Plugin can also be a listener $this->getServer()->getPluginManager()->registerEvents( $this, $this ); }}
final class EventPriority { public const LOWEST = 5; // Run first public const LOW = 4; public const NORMAL = 3; // Default public const HIGH = 2; public const HIGHEST = 1; // Run last public const MONITOR = 0; // Monitoring only, don't modify}
By default, handlers ignore cancelled events. Use @handleCancelled to receive them:
/** * @handleCancelled */public function logAllPlaceAttempts(BlockPlaceEvent $event): void { // This runs even if the event was already cancelled $this->logger->info( $event->getPlayer()->getName() . " tried to place a block (cancelled: " . ($event->isCancelled() ? "yes" : "no") . ")" );}
use pocketmine\event\Event;use pocketmine\event\Cancellable;use pocketmine\event\CancellableTrait;use pocketmine\player\Player;class PlayerLevelUpEvent extends Event implements Cancellable { use CancellableTrait; public function __construct( private Player $player, private int $oldLevel, private int $newLevel ) {} public function getPlayer(): Player { return $this->player; } public function getOldLevel(): int { return $this->oldLevel; } public function getNewLevel(): int { return $this->newLevel; } public function setNewLevel(int $level): void { $this->newLevel = $level; }}
The HandlerListManager manages all event registrations:
HandlerListManager.php:29
class HandlerListManager { public static function global(): self { return self::$globalInstance ?? (self::$globalInstance = new self()); } public function getHandlersFor(string $event): array { // Returns list of RegisteredListener objects } public function unregisterAll(RegisteredListener|Plugin|Listener|null $object = null): void { // Unregisters handlers }}
// Unregister all handlers from a plugin$pluginManager->disablePlugin($plugin);// Unregister specific listenerHandlerListManager::global()->unregisterAll($listenerInstance);
Event handlers are automatically unregistered when a plugin is disabled.
// Goodpublic function onPvP(EntityDamageByEntityEvent $event): void { }// Less efficient - receives ALL damage eventspublic function onDamage(EntityDamageEvent $event): void { if ($event instanceof EntityDamageByEntityEvent) { }}
Check hasHandlers() for Hot Paths
Avoid creating event objects if no one is listening:
if (CustomEvent::hasHandlers()) { (new CustomEvent($data))->call();}
Don't Modify in MONITOR
Use MONITOR only for logging/stats:
/** * @priority MONITOR */public function logEvent(Event $event): void { // Just log, don't modify $this->logger->info(get_class($event));}
Handle Null Cases
Not all events guarantee non-null values:
public function onDamage(EntityDamageByEntityEvent $event): void { $damager = $event->getDamager(); if ($damager instanceof Player) { // Safe to use as Player }}