Events
Register listener objects with the EventBus. Methods annotated with @EventHandler and a single Event parameter are invoked when posted.
class ExampleListener {
@EventHandler
fun onEvent(event: Event) {
// handle event
}
}
Registration:
context.eventBus.register(ExampleListener())
To stop listening, call unregister with the same listener instance.
Mod metadata
Metadata is provided via @LineageModInfo on the mod main class.
Fields:
-
id: lowercase identifier, 1-32 chars,[a-z0-9_-]+ -
name: human name, 1-64 chars,[A-Za-z0-9 _.-]+ -
version:MAJOR.MINOR.PATCH -
apiVersion:MAJOR.MINOR.PATCH -
authors: list of author names -
description: optional text -
dependencies: required dependencies -
softDependencies: optional dependencies -
website,license: optional strings
Dependency entries accept version constraints:
core
core>=1.2.0
worlds^2.0.0
If a required dependency is missing or does not satisfy the constraint, the mod will not load.
Players
PlayerManager exposes online proxy sessions. Each ProxyPlayer represents a connected player and can be moved between backends.
val player = context.players.getByName("Example")
player?.sendMessage("Hello from the proxy")
Useful fields:
-
ProxyPlayer.id -
ProxyPlayer.username -
ProxyPlayer.state -
ProxyPlayer.backendId
Transfer example:
player?.transferTo("hub-1")
Scheduler
Schedule work on the proxy runtime with Scheduler.
val handle = context.scheduler.runLater(Duration.ofSeconds(5)) {
context.logger.info("Delayed task")
}
Use runSync, runAsync, or runRepeating depending on your needs. Cancel with handle.cancel().
Getting started
Lineage mods are plain JVM jars. A mod is discovered by scanning for the @LineageModInfo annotation and instantiating the annotated class.
Minimal mod
Kotlin:
@LineageModInfo(
id = "hello",
name = "Hello Mod",
version = "1.0.0",
apiVersion = "0.1.0",
authors = ["YourName"]
)
class HelloMod : LineageMod() {
override fun onEnable() {
context.logger.info("Hello from Lineage!")
}
}
Java:
@LineageModInfo(
id = "hello",
name = "Hello Mod",
version = "1.0.0",
apiVersion = "0.1.0",
authors = {"YourName"}
)
public final class HelloMod extends LineageMod {
@Override
public void onEnable() {
context.getLogger().info("Hello from Lineage!");
}
}
Packaging
-
Build a jar that includes your mod class and resources.
-
Place the jar into
mods/next to the proxyconfig.toml. -
Per-mod data is stored under
mods/<mod-id>/.
Dependency
Lineage API is published on Maven Central.
Gradle Kotlin DSL:
dependencies {
implementation("ru.hytalemodding.lineage:api:0.1.0")
}
Gradle Groovy DSL:
dependencies {
implementation "ru.hytalemodding.lineage:api:0.1.0"
}
Maven:
<dependency>
<groupId>ru.hytalemodding.lineage</groupId>
<artifactId>api</artifactId>
<version>0.1.0</version>
</dependency>
You can still build from source and depend on the local :api module when needed.
Backends
BackendRegistry exposes the configured backend servers.
for (backend in context.backends.all()) {
context.logger.info("Backend {} -> {}:{}", backend.id, backend.host, backend.port)
}
You can move a player to a backend by id:
player.transferTo("hub-1")
Messaging
Messaging provides UDP channels for proxy and backend communication.
Raw channel:
val channel = context.messaging.registerChannel("mods:hello") { message ->
val text = message.payload.toString(Charsets.UTF_8)
context.logger.info("Got: {}", text)
}
channel.send("hi".toByteArray())
Typed channel:
val typed = MessagingChannels.registerTyped(
context.messaging,
"mods:chat",
Codecs.UTF8_STRING
) { message ->
context.logger.info("Got: {}", message.payload)
}
typed.send("hello")
Configuration
Each mod gets its own data folder under mods/<mod-id>/. Use ConfigManager to create or load TOML files in that directory.
val config = context.configManager.config(
name = "settings",
createIfMissing = true
) {
"""
enabled = true
greeting = "hello"
""".trimIndent()
}
Paths are relative to the mod folder. If name has no file extension, .toml is appended automatically.
Examples:
-
settings->mods/<id>/settings.toml -
nested/chat->mods/<id>/nested/chat.toml
Services
ServiceRegistry lets mods share instances with each other.
val key = ServiceKey(MyService::class.java)
context.serviceRegistry.register(key, MyService())
Retrieve later:
val service = context.serviceRegistry.get(key)
Lifecycle
LineageMod exposes three lifecycle hooks:
-
onLoad(context): called once after the mod is constructed and theModContextis ready. -
onEnable(): called after all mods are loaded and dependency order is resolved. -
onDisable(): called during shutdown or reload.
Use onLoad for wiring services and onEnable for runtime logic.
Commands
Commands are registered through CommandRegistry.
class PingCommand : Command {
override val name = "ping"
override val aliases = listOf("pong")
override val description = "Basic connectivity test."
override val permission: String? = null
override fun execute(context: CommandContext) {
context.sender.sendMessage("pong")
}
override fun suggest(context: CommandContext): List<String> = emptyList()
}
Register from your mod:
context.commandRegistry.register(PingCommand())
Use CommandContext.hasPermission(...) if you want to handle permission checks manually. CommandSender.type is CONSOLE, PLAYER, or SYSTEM.
Permissions
Use PermissionChecker to evaluate permission strings against a subject.
if (context.permissionChecker.hasPermission(sender, "lineage.example.use")) {
sender.sendMessage("Allowed")
}
CommandSender implements PermissionSubject, so it can be checked directly.
Lineage Modding
This documentation covers the public API used to build Lineage mods. The same Markdown files are used for Dokka and GitHub docs to keep them in sync.
Contents
-
getting-started.md
-
mod-metadata.md
-
lifecycle.md
-
commands.md
-
events.md
-
players.md
-
messaging.md
-
config.md
-
permissions.md
-
scheduler.md
-
services.md
-
backends.md