← Roadmap

Events & coroutines

Events and coroutines

Why

A Bukkit listener is a class with @EventHandler, registration in PluginManager and a separate file per group. A delayed task is a BukkitRunnable with manual tick counting, and a three-phase chain becomes three nested Runnables.

The framework gives two things: event<T> { } — a one-line listener, and MCCoroutine — plugin.launch { delay(20.ticks) } instead of schedulers. The framework itself is written entirely on them.

Minimal example

Listeners
override fun onEnable() {
    event<PlayerJoinEvent> { it.joinMessage(null) }

    event<BlockBreakEvent>(priority = EventPriority.HIGH, ignoreCancelled = true) {
        if (it.block.type == Material.BEDROCK) it.isCancelled = true
    }

    val listener = event<PlayerMoveEvent> { … }
    listener.unregister()
}

Inside the plugin class — event<T>, elsewhere — MyPlugin.instance.event<T>. The listener is bound to the plugin and removed on unload.

Coroutines
MyPlugin.instance.launch {
    player.sendMessage("3")
    delay(20.ticks)
    player.sendMessage("2")
    delay(20.ticks)
    player.sendMessage("1")
    delay(20.ticks)
    Arena.start()
}

val job = MyPlugin.instance.launch {
    while (true) {
        Arena.tick()
        delay(1.ticks)
    }
}
job.cancel()

In the child build.gradle.kts: compileOnly("com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:2.22.0") — the gradle plugin already handles relocation.

Pitfalls

  • Inside the event<T> lambda the event is it. Name parameters in nested lambdas, otherwise it gets shadowed.
  • return from a handler — return@event.
  • launch runs on the main server thread — Bukkit API is safe; for heavy work use withContext(Dispatchers.IO) and back.
  • An endless while in launch without delay will hang the server.