Skip to main content

Running a server? This topic in the server admin guide

Installation

Versatia has two halves. The server needs VersatiaCore; your build needs VersatiaAPI, which comes with VersatiaStarter.

On the server Server admin​

The server needs Paper 1.21.4 on Java 21, VersatiaCore in plugins/, and the modules your plugin uses. Installing, configuring and updating them is covered by the server admin guide, which is also the page to send to the people who run your plugin.

What matters for your build: VersatiaCore is the only plugin that ships the Kotlin runtime. Its plugin.yml lists kotlin-stdlib, kotlin-reflect and kotlinx-coroutines-core-jvm under libraries:, and every Versatia plugin reaches Kotlin and kotlinx.coroutines through VersatiaCore, which is why your plugin must not list either itself (see below). A plugin built against a VersatiaAPI needs the VersatiaCore of the same release; any other refuses it with the descriptor format message.

Where do the JARs come from?

Versatia is not published to a Maven repository yet. VersatiaAPI and VersatiaCore are open source: build VersatiaAPI and publish it to your local Maven repository, then build the VersatiaCore plugin JAR; the steps are in Building the framework.

In your build Plugin developer​

A Versatia plugin is an ordinary Kotlin/JVM Gradle project with the KSP plugin applied. The repositories are declared once, in settings.gradle.kts.

import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
kotlin("jvm") version "2.4.20"
id("com.google.devtools.ksp") version "2.3.12"
}

group = "com.example"
version = "1.0.0"

val versatiaVersion = "0.0.1"

dependencies {
implementation("dev.versatia:VersatiaStarter:$versatiaVersion")
ksp("dev.versatia:VersatiaAPI-ksp:$versatiaVersion")
}

java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}

kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_21
}
}

Two dependencies, nothing else:

DependencyConfigurationRole
VersatiaStarterimplementationthe starter, in the spirit of Spring Boot's: no code, it exports VersatiaAPI (annotations, VersatiaPlugin, utilities, and with it the Paper API, kotlin-stdlib, kotlin-reflect and kotlinx-coroutines-core, pinned to the version VersatiaCore loads) and the API of every module, so you declare none of those
VersatiaAPI-kspkspthe code generator. Lives on the annotation-processing classpath only and never reaches the server; it cannot come through the starter, because Gradle does not fill the ksp configuration transitively

Keep the version in one versatiaVersion value; both lines use it. A plugin that uses no module may depend on dev.versatia:VersatiaAPI instead of the starter.

Versions from VersatiaCatalog​

Instead of writing the Kotlin, KSP and Versatia versions yourself, you can import the Gradle version catalog the framework is built with, dev.versatia:VersatiaCatalog (published by VersatiaAPI). The examples and the template do this. The Versatia version moves to gradle.properties, the one place both scripts read it from; settings.gradle.kts imports the catalog inside dependencyResolutionManagement:

versatiaVersion=0.0.1

The catalog matches the framework release: Kotlin and KSP at the versions Versatia is compiled with, plus libs.paper.api (for testImplementation), libs.versatia.api, libs.versatia.database, libs.commandapi.*, libs.snakeyaml and libs.kotlinx.coroutines.test (for testImplementation; kotlinx-coroutines-core itself comes with VersatiaAPI). Your own libraries stay in your build, or in a second catalog of your own.

There is no configuration block for the processor. It finds your plugin's main class on its own and places the generated descriptor next to it.

Nothing the server provides goes into your JAR

VersatiaAPI, Kotlin, the Paper API and the module APIs are on the server already: VersatiaCore, Paper and the module plugins provide them, and your plugin reaches them through depend:. A second copy in your JAR is a second class of the same name and breaks at runtime. The starter exports everything with compileOnlyApi, so it is on the compile classpath only: a plain jar does not pack it, and neither does Shadow when you shade a third-party library. A test that needs these classes at runtime adds them with testImplementation. The same rule covers another plugin you inject beans from, which you add as compileOnly.

plugin.yml Plugin developer​

name: MyPlugin
version: '1.0.0'
main: com.example.myplugin.MyPlugin
api-version: '1.21'
depend:
- VersatiaCore

Two rules:

  • VersatiaCore goes under depend:. It guarantees that the runtime is up before your plugin enables and lets your plugin load the Kotlin and VersatiaAPI classes from VersatiaCore.

  • No libraries: entry for Kotlin, nor for a library built on it. Paper loads each plugin's libraries into a separate class loader. A plugin with its own copy of Kotlin would exchange kotlin.jvm.functions.Function1 objects with VersatiaCore across two different Function1 classes, and the JVM rejects that with a LinkageError. A Kotlin library such as kotlinx-serialization-json brings kotlin-stdlib along, so listing it under libraries: has the same effect. Shade such a library into your JAR instead and leave Kotlin out:

    Wrong: plugin.yml loads a second Kotlin
    depend:
    - VersatiaCore
    libraries:
    - org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0
    Right: build.gradle.kts shades the library without Kotlin
    plugins {
    id("com.gradleup.shadow") version "8.3.5"
    }

    dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
    }

    tasks.shadowJar {
    dependencies {
    exclude(dependency("org.jetbrains.kotlin:.*"))
    exclude(dependency("org.jetbrains.kotlinx:kotlinx-coroutines-.*"))
    }
    }
  • No copy of kotlinx.coroutines at all, neither under libraries: nor shaded, nor through a coroutine library for Bukkit such as MCCoroutine. VersatiaCore loads it, VersatiaAPI exports it to your build at that same version, and a PluginScope or a Condition is a kotlinx.coroutines type crossing between your plugin and VersatiaCore, so a second copy fails with a LinkageError or a ClassCastException.

    See Troubleshooting.

plugin.yml, not paper-plugin.yml

A Versatia plugin is described by plugin.yml. A paper-plugin.yml makes Paper load the JAR as a Paper plugin, even next to a plugin.yml: its class loader is isolated, and it has no depend: through which VersatiaCore shares Kotlin and VersatiaAPI the way it does with a Bukkit plugin. That loading is not supported, and the checkPluginYml task of the template fails the build when src/main/resources/paper-plugin.yml exists. See Coming from Bukkit.

Check the setup​

Run ./gradlew build. On success the build directory contains

build/generated/ksp/main/kotlin/com/example/myplugin/VersatiaDescriptor.kt

in the package of your main class. It already lists the elements every plugin inherits from VersatiaPlugin (the main class as a bean, resource extraction, the binding of the plugin's coroutine scope to its start and stop); beans and auto-invoked functions add theirs, which is the subject of Your first plugin. The other beans every plugin has, such as its Logger and PluginScope, are not listed: VersatiaCore's own descriptor exposes them, so a parameter of their type is recorded like any other dependency.