KPaper provides a powerful and intuitive event system that simplifies Bukkit event handling while adding advanced features like custom events and event priorities.
The most common way to handle events in KPaper is using the listen function:
import cc.modlabs.kpaper.event.listen
import org.bukkit.event.player.PlayerJoinEvent
class MyPlugin : KPlugin() {
override fun startup() {
// Basic event listener
listen<PlayerJoinEvent> { event ->
val player = event.player
player.sendMessage("Welcome ${player.name}!")
}
}
}You can specify event priorities to control execution order:
import org.bukkit.event.EventPriority
// High priority - executes early
listen<PlayerJoinEvent>(priority = EventPriority.HIGH) { event ->
logger.info("${event.player.name} is joining (high priority)")
}
// Low priority - executes late
listen<PlayerJoinEvent>(priority = EventPriority.LOW) { event ->
logger.info("${event.player.name} joined (low priority)")
}Only handle events when certain conditions are met:
// Only handle events for specific players
listen<PlayerJoinEvent> { event ->
if (event.player.hasPermission("vip.join")) {
event.player.sendMessage("Welcome VIP player!")
// Give VIP welcome items
}
}
// Handle events in specific worlds
listen<BlockBreakEvent> { event ->
if (event.block.world.name == "mining_world") {
// Special mining world logic
event.player.giveExp(1)
}
}Easily cancel events based on conditions:
listen<PlayerInteractEvent> { event ->
if (event.player.world.name == "spawn") {
if (!event.player.hasPermission("spawn.interact")) {
event.isCancelled = true
event.player.sendMessage("You cannot interact in spawn!")
}
}
}Register multiple handlers for the same event:
// Handler 1: Logging
listen<PlayerJoinEvent> { event ->
logger.info("Player ${event.player.name} joined from ${event.player.address?.address}")
}
// Handler 2: Welcome message
listen<PlayerJoinEvent> { event ->
event.player.sendMessage("Welcome to our server!")
}
// Handler 3: First-time player setup
listen<PlayerJoinEvent> { event ->
if (!event.player.hasPlayedBefore()) {
giveStarterKit(event.player)
}
}KPaper makes it easy to create and dispatch custom events:
import cc.modlabs.kpaper.event.KEvent
import org.bukkit.entity.Player
import org.bukkit.event.HandlerList
class PlayerLevelUpEvent(
val player: Player,
val oldLevel: Int,
val newLevel: Int
) : KEvent() {
companion object {
private val HANDLERS = HandlerList()
@JvmStatic
fun getHandlerList(): HandlerList = HANDLERS
}
override fun getHandlers(): HandlerList = HANDLERS
}import cc.modlabs.kpaper.event.callEvent
fun levelUpPlayer(player: Player, newLevel: Int) {
val oldLevel = getPlayerLevel(player)
// Dispatch the custom event
val event = PlayerLevelUpEvent(player, oldLevel, newLevel)
event.callEvent()
// Continue with level up logic if event wasn't cancelled
if (!event.isCancelled) {
setPlayerLevel(player, newLevel)
}
}listen<PlayerLevelUpEvent> { event ->
val player = event.player
val newLevel = event.newLevel
player.sendMessage("Congratulations! You reached level $newLevel!")
// Give rewards based on level
when (newLevel) {
10 -> player.inventory.addItem(ItemStack(Material.DIAMOND, 5))
25 -> player.inventory.addItem(ItemStack(Material.NETHERITE_INGOT, 1))
50 -> {
player.sendMessage("You've reached level 50! Here's a special reward!")
// Give special reward
}
}
}For more complex event handling, you can create dedicated event handler classes:
import cc.modlabs.kpaper.event.EventHandler
class PlayerEventHandler : EventHandler() {
override fun load() {
// Register events when handler is loaded
listen<PlayerJoinEvent> { handlePlayerJoin(it) }
listen<PlayerQuitEvent> { handlePlayerQuit(it) }
listen<PlayerDeathEvent> { handlePlayerDeath(it) }
}
override fun unload() {
// Cleanup when handler is unloaded
logger.info("Player event handler unloaded")
}
private fun handlePlayerJoin(event: PlayerJoinEvent) {
val player = event.player
// Update join message
event.joinMessage(Component.text("${player.name} has entered the realm!")
.color(NamedTextColor.GREEN))
// First time player setup
if (!player.hasPlayedBefore()) {
setupNewPlayer(player)
}
// Update player data
updatePlayerData(player)
}
private fun handlePlayerQuit(event: PlayerQuitEvent) {
val player = event.player
// Update quit message
event.quitMessage(Component.text("${player.name} has left the realm!")
.color(NamedTextColor.YELLOW))
// Save player data
savePlayerData(player)
}
private fun handlePlayerDeath(event: PlayerDeathEvent) {
val player = event.entity
val killer = player.killer
if (killer != null) {
// PvP death
event.deathMessage(Component.text("${player.name} was slain by ${killer.name}")
.color(NamedTextColor.RED))
} else {
// Environmental death
event.deathMessage(Component.text("${player.name} met an unfortunate end")
.color(NamedTextColor.DARK_RED))
}
// Custom death handling
handleDeathRewards(player, killer)
}
}class MyPlugin : KPlugin() {
private lateinit var playerHandler: PlayerEventHandler
override fun startup() {
// Register the event handler
playerHandler = PlayerEventHandler()
playerHandler.load()
}
override fun shutdown() {
// Unload the event handler
if (::playerHandler.isInitialized) {
playerHandler.unload()
}
}
}Create reusable event filters:
fun <T : Event> isInWorld(worldName: String): (T) -> Boolean = { event ->
when (event) {
is PlayerEvent -> event.player.world.name == worldName
is BlockEvent -> event.block.world.name == worldName
is EntityEvent -> event.entity.world.name == worldName
else -> false
}
}
// Usage
listen<PlayerInteractEvent>(filter = isInWorld("pvp_arena")) { event ->
// Only handle interactions in PvP arena
handlePvPInteraction(event)
}Handle events after a delay:
import cc.modlabs.kpaper.coroutines.taskRunLater
listen<PlayerJoinEvent> { event ->
val player = event.player
// Send welcome message after 3 seconds
taskRunLater(60) { // 60 ticks = 3 seconds
if (player.isOnline) {
player.sendMessage("Hope you're enjoying the server!")
}
}
}Track event statistics:
class EventMetrics {
private val eventCounts = mutableMapOf<String, Int>()
fun trackEvent(eventName: String) {
eventCounts[eventName] = eventCounts.getOrDefault(eventName, 0) + 1
}
fun getEventCount(eventName: String): Int = eventCounts.getOrDefault(eventName, 0)
fun getAllCounts(): Map<String, Int> = eventCounts.toMap()
}
val metrics = EventMetrics()
listen<PlayerJoinEvent> { event ->
metrics.trackEvent("player_join")
// Handle join...
}
listen<PlayerQuitEvent> { event ->
metrics.trackEvent("player_quit")
// Handle quit...
}class PlayerManager {
private val playerData = mutableMapOf<UUID, PlayerData>()
fun initialize() {
// Load player on join
listen<PlayerJoinEvent> { event ->
val player = event.player
playerData[player.uniqueId] = loadPlayerData(player.uniqueId)
}
// Save player on quit
listen<PlayerQuitEvent> { event ->
val player = event.player
playerData[player.uniqueId]?.let { data ->
savePlayerData(player.uniqueId, data)
}
playerData.remove(player.uniqueId)
}
// Auto-save periodically
listen<PlayerMoveEvent> { event ->
if (event.hasChangedBlock()) {
updateLastLocation(event.player, event.to)
}
}
}
}fun setupWorldProtection() {
val protectedWorlds = setOf("spawn", "hub", "tutorial")
// Prevent block breaking in protected worlds
listen<BlockBreakEvent> { event ->
if (event.block.world.name in protectedWorlds) {
if (!event.player.hasPermission("worldprotect.bypass")) {
event.isCancelled = true
event.player.sendMessage("You cannot break blocks in this world!")
}
}
}
// Prevent block placing in protected worlds
listen<BlockPlaceEvent> { event ->
if (event.block.world.name in protectedWorlds) {
if (!event.player.hasPermission("worldprotect.bypass")) {
event.isCancelled = true
event.player.sendMessage("You cannot place blocks in this world!")
}
}
}
}class AntiGriefSystem {
private val recentBreaks = mutableMapOf<UUID, MutableList<BlockBreakRecord>>()
fun initialize() {
listen<BlockBreakEvent> { event ->
val player = event.player
val record = BlockBreakRecord(
location = event.block.location,
material = event.block.type,
timestamp = System.currentTimeMillis()
)
recentBreaks.computeIfAbsent(player.uniqueId) { mutableListOf() }
.add(record)
// Check for suspicious activity
if (isSuspiciousActivity(player.uniqueId)) {
handleSuspiciousActivity(player, event)
}
// Cleanup old records
cleanupOldRecords(player.uniqueId)
}
}
private fun isSuspiciousActivity(playerId: UUID): Boolean {
val breaks = recentBreaks[playerId] ?: return false
val recentBreaks = breaks.filter {
System.currentTimeMillis() - it.timestamp < 60000 // Last minute
}
return recentBreaks.size > 50 // More than 50 blocks per minute
}
}// Good - specific event
listen<PlayerInteractEntityEvent> { event ->
if (event.rightClicked is Villager) {
// Handle villager interaction
}
}
// Avoid - too general
listen<PlayerInteractEvent> { event ->
// Handles all interactions, less efficient
}listen<EntityDamageByEntityEvent> { event ->
val damager = event.damager
val victim = event.entity
// Safe casting
if (damager is Player && victim is Player) {
handlePvPDamage(damager, victim, event.damage)
}
}listen<PlayerMoveEvent> { event ->
// Avoid heavy operations in frequently called events
if (event.hasChangedBlock()) { // Only check when player changes blocks
checkPlayerRegion(event.player)
}
}listen<PlayerInteractEvent> { event ->
if (shouldCancelInteraction(event)) {
event.isCancelled = true
event.player.sendMessage("Interaction cancelled!")
return@listen // Exit early after cancellation
}
// Continue with normal logic
handleInteraction(event)
}- Ensure the event class is imported correctly
- Check that the plugin is properly registered
- Verify event priority conflicts
- Avoid heavy operations in frequently called events (like PlayerMoveEvent)
- Use event filtering to reduce unnecessary processing
- Profile your event handlers to identify bottlenecks
- Clean up collections in event handlers
- Remove event listeners when no longer needed
- Avoid storing references to event objects
- Plugin Development - Core KPaper concepts
- Extensions - Kotlin extensions for events
- Coroutines - Async event handling
- Examples - Common event patterns