← Roadmap

Setup

Setup

Why

Requirements: Paper 1.21.4+ (26.x included), Java 21, Kotlin 2.2. The framework is installed on the server as a regular plugin; the Kotlin runtime and coroutines live inside it.

A child plugin does not depend on the framework directly. It applies the ru.hukm.effective-plugin gradle plugin, which brings Kotlin, shadow, relocation of kotlin classes, the Paper API and a compileOnly dependency on the latest EffectiveSpigot.

On the server EffectiveSpigot is a separate plugin and your jar lists it in depend. The Kotlin runtime and coroutines already live inside the framework — they are not bundled into your jar.

Minimal example

settings.gradle.kts
pluginManagement {
    repositories {
        gradlePluginPortal()
        maven("https://maven.hukm.dev/repository/maven-public/")
    }
}
rootProject.name = "MyPlugin"
build.gradle.kts
plugins {
    kotlin("jvm") version "2.2.0"
    id("com.gradleup.shadow") version "8.3.6"
    id("ru.hukm.effective-plugin") version "1.0.0-SNAPSHOT"
}

group = "ru.example"
version = "1.0.0"

The version in id("ru.hukm.effective-plugin") is the gradle plugin version, not the framework. The framework resolves as latest.integration automatically.

plugin.yml
name: MyPlugin
version: '${version}'
main: ru.example.myplugin.MyPlugin
api-version: '1.21'
depend: [EffectiveSpigot]
Main class
class MyPlugin : JavaPlugin() {
    companion object {
        lateinit var instance: MyPlugin
            private set
    }

    override fun onLoad() {
        instance = this
        MyCommand.init()
    }

    override fun onEnable() {
        RubyItem.init()
        MyMenu.init()

        EffectiveResourcepack.addServerResourcepack(this, "", "")
    }
}

Every Effective* object is lazy: until something touches it, it is neither created nor registered. That is why each has an init() called from onEnable (commands — from onLoad). addServerResourcepack at the end is required for anything that uses the resource pack (item and block textures, glyphs, textured menus): without it no pack is built for the plugin. Empty url and sha1 — when the framework's built-in HTTP server serves the pack.

Pitfalls

  • A forgotten init() is the most common cause of "the item is not in /egive". The object is not created, so it is not registered.
  • A forgotten addServerResourcepack — the item exists but has no texture: no pack was built for the plugin. Call it after init() of all items and blocks.
  • Commands are registered in onLoad, not onEnable — Brigadier only accepts them during the load phase.
  • If you add your own relocate rules to shadow, they must match the framework relocation (ru.hukm.effectiveSpigot.libs.*), otherwise Kotlin classes get duplicated.
  • After the gradle plugin itself is republished, build the child project once with --refresh-dependencies.
  • Built-in debug commands: /egive, /emob, /ecomposite, /emenu, /ezone, /escreen — everything registered shows up in tab completion.