Files
Documentation/content/core-concepts/events/event-priorities.en.md
2026-01-20 20:33:59 +01:00

5.8 KiB

title, type, weight
title type weight
Event Priorities docs 2

Event priorities control the order in which event listeners are called. This is important when multiple plugins or handlers need to react to the same event.

Priority Values

Hytale provides five priority levels:

Priority Value Description
FIRST -21844 Runs first, before all others
EARLY -10922 Runs early, after FIRST
NORMAL 0 Default priority
LATE 10922 Runs late, after NORMAL
LAST 21844 Runs last, after all others

{{< callout type="info" >}} Lower values run first. The values are spread across the short range to allow for custom priorities between the standard levels. {{< /callout >}}

Using EventPriority

With Enum

getEventRegistry().register(
    EventPriority.EARLY,
    PlayerConnectEvent.class,
    event -> {
        // This runs before NORMAL handlers
    }
);

getEventRegistry().register(
    EventPriority.LATE,
    PlayerConnectEvent.class,
    event -> {
        // This runs after NORMAL handlers
    }
);

With Custom Value

// Use a custom priority value (between -32768 and 32767)
short customPriority = -5000;  // Between EARLY and NORMAL

getEventRegistry().register(
    customPriority,
    PlayerConnectEvent.class,
    event -> {
        // Custom priority handler
    }
);

Execution Order

When an event is fired, handlers are called in priority order:

FIRST (-21844)
   ↓
EARLY (-10922)
   ↓
NORMAL (0)      ← Default
   ↓
LATE (10922)
   ↓
LAST (21844)

Example

@Override
public void start() {
    getEventRegistry().register(EventPriority.LAST, PlayerReadyEvent.class, event -> {
        getLogger().at(Level.INFO).log("3. LAST handler");
    });

    getEventRegistry().register(EventPriority.FIRST, PlayerReadyEvent.class, event -> {
        getLogger().at(Level.INFO).log("1. FIRST handler");
    });

    getEventRegistry().register(PlayerReadyEvent.class, event -> {
        getLogger().at(Level.INFO).log("2. NORMAL handler (default)");
    });
}

// Output when player is ready:
// 1. FIRST handler
// 2. NORMAL handler (default)
// 3. LAST handler

When to Use Each Priority

FIRST

Use for:

  • Setting up event data before other handlers
  • Logging/debugging that should see the original event
  • Critical validation that must run before anything else
getEventRegistry().register(EventPriority.FIRST, PlayerChatEvent.class, event -> {
    // Log original message before any filtering
    logChat(event.getSender(), event.getContent());
});

EARLY

Use for:

  • Modifications that other plugins should see
  • Permission checks before regular handlers
  • Data preprocessing
getEventRegistry().register(EventPriority.EARLY, PlayerChatEvent.class, event -> {
    // Filter profanity before other plugins process the message
    String filtered = filterProfanity(event.getContent());
    event.setContent(filtered);
});

NORMAL

Use for:

  • Standard event handling
  • Most plugin functionality
  • Default when priority doesn't matter
getEventRegistry().register(PlayerChatEvent.class, event -> {
    // Standard chat processing
    processMessage(event.getSender(), event.getContent());
});

LATE

Use for:

  • Reactions to modifications made by other handlers
  • Analytics/statistics gathering
  • Secondary effects
getEventRegistry().register(EventPriority.LATE, PlayerChatEvent.class, event -> {
    // Record the final message after all modifications
    recordChatHistory(event.getSender(), event.getContent());
});

LAST

Use for:

  • Final cleanup or overrides
  • Monitoring cancellation status
  • Logging final event state
getEventRegistry().register(EventPriority.LAST, PlayerChatEvent.class, event -> {
    if (event.isCancelled()) {
        getLogger().at(Level.INFO).log("Chat was cancelled by another plugin");
    }
});

With Cancellable Events

Priority is especially important with cancellable events:

// FIRST handler might cancel
getEventRegistry().register(EventPriority.FIRST, BreakBlockEvent.class, event -> {
    if (isProtectedArea(event.getTargetBlock())) {
        event.setCancelled(true);
    }
});

// NORMAL handler should check cancellation
getEventRegistry().register(BreakBlockEvent.class, event -> {
    if (!event.isCancelled()) {
        // Process block break
        trackBlockBreak(event.getTargetBlock(), event.getBlockType());
    }
});

// LAST handler for monitoring
getEventRegistry().register(EventPriority.LAST, BreakBlockEvent.class, event -> {
    if (event.isCancelled()) {
        logProtectedBlockAttempt(event.getTargetBlock());
    }
});

Async Events and Priority

Async events also support priorities:

getEventRegistry().registerAsync(
    EventPriority.EARLY,
    PlayerChatEvent.class,
    future -> future.thenApply(event -> {
        // Early async processing
        event.setContent(filterContent(event.getContent()));
        return event;
    })
);

getEventRegistry().registerAsync(
    EventPriority.LATE,
    PlayerChatEvent.class,
    future -> future.thenApply(event -> {
        // Late async processing - sees modified content
        logFinalMessage(event.getSender(), event.getContent());
        return event;
    })
);

Best Practices

{{< callout type="tip" >}} Priority Guidelines:

  • Use FIRST sparingly - only for logging or critical checks
  • Protection/permission plugins should use EARLY
  • Feature plugins should use NORMAL (default)
  • Analytics/logging should use LATE
  • Use LAST only when you need to see the final event state {{< /callout >}}

{{< callout type="warning" >}} Avoid Priority Conflicts:

  • Document your plugin's priority choices
  • Check cancellation status at NORMAL and later priorities
  • Don't rely on running "after" specific plugins {{< /callout >}}