Skip to content

Repository files navigation

MawuExample-Paper

A Paper plugin whose commands and events are written in Lua.

Paper Java Build Lua License

Overview

This is the smallest useful thing you can build with Mawu: a Paper plugin where the Kotlin is wiring and the behaviour lives in src/main/lua.

The scripts are compiled to Luak bytecode at build time and packaged inside the jar, so the server never parses Lua at startup.

What it does

Every command and every listener below is a Lua script. Nothing is declared in plugin.yml.

Command What it does
/greet [player] Greets someone, or you
/luainfo Reports the runtime, the server and how full it is
/whois [player] Name, world, position, mode, health, ping
/heal [player] Fills health, hunger and saturation
/mode <mode> Changes your game mode
/buff <effect> A minute of speed, jump, night vision or strength
/kit A starter kit, with a cooldown kept in Lua
/wand An enchanted named stick that inspects blocks
/go <player> or /go <x> <y> <z> Teleports you
/sethome, /home, /delhome A home, saved in the plugin config
/setwarp <name>, /warp [name] Warps everyone can travel to
/msg <player> <text>, /r <text> Private messages, with a reply target
/sky <moment> Sets the time and clears the weather
/announce <text> A coloured broadcast
Listener What it does
Join and quit Welcome message, and shorter join and quit lines
First join Sends new players to spawn with a sound
Death Rewrites the death message and keeps the inventory
Chat Cancels a message with a banned word in it
Blocks Protects bedrock, spawners and frames; stops TNT
Interact The other half of the wand
Damage No fighting in the world called lobby
Weather and spawning No rain in the lobby, no natural phantoms
Server list ping A gradient MOTD

The Lua

Scripts live in src/main/lua, in whatever shape suits them:

src/main/lua/
  commands/greet.lua
  commands/home.lua
  ...
  events/join.lua
  events/blocks.lua
  ...

A command:

local MiniMessage = import 'net.kyori.adventure.text.minimessage.MiniMessage'
local text = MiniMessage:miniMessage()

commands.register('greet', 'Greets a player by name', function(sender, args)
    local who = args[1] or sender:getName()
    sender:sendMessage(text:deserialize('<green>Hello, <white>' .. who .. '</white>!'))
end)

An event:

local PlayerJoinEvent = import 'org.bukkit.event.player.PlayerJoinEvent'

events.on(PlayerJoinEvent, function(event)
    local player = event:getPlayer()
    player:sendMessage(text:deserialize('<gold>Welcome, ' .. player:getName() .. '!'))
    event:joinMessage(text:deserialize('<green>+ <white>' .. player:getName()))
end)

Nothing is registered from Kotlin for those two names. A script reaches the Paper API, Adventure and the JDK by name, on the same classpath the Kotlin next to it uses.

The Kotlin

ExamplePlugin is the plain Java entry point and does nothing but fetch the libraries. LuaPlugin is the wiring: it builds one MawuLane, hands the scripts what they cannot name themselves, and runs everything Mawu packaged.

lane.expose("server", plugin.server)
lane.expose("plugin", plugin)
lane.exposeFunctions("commands", mapOf("register" to MawuFunction(::registerCommand)))
lane.exposeFunctions("events", mapOf("on" to MawuFunction(::listenTo)))

for (id in MawuScripts.ids(LuaPlugin::class.java.classLoader)) {
    lane.run(id)
}

commands.register collects what a script asks for and hands it to Paper's LifecycleEvents.COMMANDS registrar as a BasicCommand; events.on takes the event class the script imported and registers an ordinary Bukkit listener that calls back into Lua.

That listener checks the event type before calling the script. An event class that declares no handler list of its own shares its parent's, so a listener for EntityDamageByEntityEvent is handed every EntityDamageEvent as well, and a script asking about a fight would be woken by a falling anvil.

What the build checks

The Lua is compiled with the rest of the project, against the same classpath, so a name that does not exist fails the build rather than the server:

> Unknown classes:
  src/main/lua/events/join.lua:4: no class named 'org.bukkit.event.player.PlayerJoinEventt'
  src/main/lua/commands/greet.lua:8: 'net.kyori.adventure.text.minimessage.MiniMessage' has no member 'deserializee'

Both of those came up while these scripts were being written, along with Player:isInstance(entity), which reads like Java but asks a Bukkit interface for a method of java.lang.Class. The build named it; the server would have waited for somebody to swing a sword.

The globals the plugin installs are declared in build.gradle.kts, so reading one the plugin does not install fails the same way:

mawu {
    knownGlobals.addAll("server", "plugin", "commands", "events")
}

Two things it cannot check. A call on one of those globals, since only the plugin knows what they hold: server:getMinecraftVersion() is checked no further than the name server being known. And argument types, so player:showTitle(component) passes the build and fails at runtime, because that method wants a Title and not a Component.

Building

./gradlew build

The jar lands in build/libs/ at around 25 KB. Drop it into a Paper server's plugins/ folder.

Libraries at boot

Mawu, Luak and the Kotlin standard library are downloaded the first time the plugin runs and cached under plugins/MawuExample/libraries, the way BlueArcade and BlueHousing do it. ExampleRuntimeDependencies lists them and ExamplePlugin fetches them before anything that needs them is loaded.

The one library that cannot arrive that way is the downloader itself, so net.blueva.foundation.dependencies is the only package packaged into the jar. That is also why the entry point is plain Java: kotlin-stdlib is not on the classpath until it has run.

This needs the legacy plugin.yml rather than paper-plugin.yml: injecting a jar wants a URLClassLoader, and Paper's modern plugin class loader is not one.

Requirements

Component Requirement
JDK 25, as Paper 26.2 requires
Server Paper 26.2
Network Reachable on first boot, to fetch the libraries

License

MIT. See LICENSE.

About

A Paper plugin whose commands and events are written in Lua, compiled into the jar by Mawu

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages