Skip to content

Repository files navigation

Minecraft Lua Scripting

A Minecraft plugin that enables dynamic Lua scripting for Bukkit/Paper servers using LuaJ. Register commands and event handlers directly from Lua scripts without modifying plugin.yml.

Installation

  1. Download the latest JAR from the releases page
  2. Place the JAR in your server's plugins/ folder
  3. Restart the server
  4. The scripts/ folder is created automatically at plugins/MinecraftLuaScripting/scripts/
  5. Add your Lua scripts (.lua files) to the scripts folder
  6. Use /mls reloadscripts or restart the server

Compatibility

This plugin supports Minecraft 1.18 and all newer versions when running on Paper servers. The plugin requires Java 17 or higher.

Quick Start

Create a file plugins/MinecraftLuaScripting/scripts/hello.lua:

-- Register a simple command
registerCommand("hello", "myplugin.hello", function(sender, args)
    sender:sendMessage("Hello from Lua!")
    if #args > 0 then
        sender:sendMessage("You said: " .. args[1])
    end
end)

-- Register an event handler
registerEvent("org.bukkit.event.player.PlayerJoinEvent", function(event)
    event:getPlayer():sendMessage("Welcome! This message is from Lua!")
end)

API Documentation

  • Adventure API - Advanced text formatting, boss bars, titles, action bars, sounds
  • Debug API - Debug logging, performance timing, table inspection
  • Entity API - Spawn, teleport, damage, and manipulate entities
  • PDC API - Persistent Data Container support for items, entities, and blocks
  • Inventory API - Create and manage inventories, custom GUIs, item manipulation
  • Player API - Teleport, give items, get/set health, food, experience, gamemode
  • World API - Get/set blocks, spawn particles, control time/weather, explosions, lightning
  • Scheduler API - Schedule delayed and repeating tasks (sync and async)
  • Reflection API - Load Java classes, create instances, call static methods, cast objects
  • Registration API - Register commands and event handlers dynamically

Developer API

  • Addon API - Create external Java plugins that extend MinecraftLuaScripting with custom Lua API tables

Commands

/mls (alias: /minecraftluascripting)

Permission: minecraftluascripting.reload

Manage the plugin and reload scripts in-game without restarting the server.

Subcommands:

Subcommand Arguments Description
reloadscript <script name> Reload a single script (.lua extension is optional)
reloadscripts Unload and reload all scripts
reloadconfig Reload configuration file (including debug mode). A changed script-timeout-ms is read here but only applies from the next reloadscripts
viewaddons List all addons currently registered with MinecraftLuaScripting

Examples:

/mls reloadscript hello
/mls reloadscripts
/mls reloadconfig
/mls viewaddons

Configuration

The plugin creates a config.yml file in plugins/MinecraftLuaScripting/ with the following settings:

debug-mode: true
script-timeout-ms: 5000

Debug Mode:

  • When true: Verbose logging including script loading details, debug API output, and performance information
  • When false: Only critical errors and warnings are logged
  • Can be toggled at runtime using /mls reloadconfig
  • Default: true

Script Timeout:

  • How long a single entry into Lua may run on the main server thread before it is aborted with an error
  • Applies separately to each entry: a script's top level, an event handler, a command, a scheduler callback (synchronous or asynchronous)
  • Scripts run on the main thread, so one that never returns would otherwise freeze the server outright; this turns that into a logged error naming the script and the line it was stuck on
  • The budget is checked between Lua instructions, so it stops runaway Lua (infinite loops, unbounded recursion). It cannot interrupt a script blocked inside a Java call, since no Lua instruction runs while that call is on the stack
  • The abort cannot be caught by pcall
  • Set to 0 to disable. Changes take effect on the next script reload
  • Default: 5000

Script Loading

  • Scripts are loaded from plugins/MinecraftLuaScripting/scripts/
  • Scripts can be organized in subdirectories (e.g., scripts/commands/teleport.lua)
  • Only files ending with .lua are loaded
  • Files starting with -- are ignored (useful for disabling scripts)
  • Scripts are loaded recursively from all subdirectories
  • Script names preserve their relative path (e.g., commands/teleport.lua)
  • If a script fails to load, an error is logged but other scripts continue loading
  • A failed load is rolled back: everything the script registered before failing (commands, event handlers, scheduled tasks) is discarded, and the previously loaded version of that script, if any, keeps running untouched
  • This covers a script that overruns script-timeout-ms at its top level, or that exhausts the JVM stack through runaway recursion; both fail the load cleanly rather than leaving half of the script registered

API Guardrails

All scripts share a single Lua state, so one script tampering with an API would break every other script. The plugin therefore locks the official API globals in place. This is an integrity guarantee between cooperating scripts, not a sandbox — see Security.

Protected globals

class, castTo, callStatic, newInstance, registerCommand, registerEvent, AdventureApi, InventoryApi, EntityApi, PlayerApi, WorldApi, SchedulerApi, DebugApi, PdcApi, plus every API table registered by an addon.

The built-ins are declared once, in BuiltInApis.ALL; installing them, locking their names, refusing scripts that shadow them, and rejecting addons that collide with them all derive from that single list.

What is rejected

Attempt Example Result
Rebinding the global EntityApi = {} attempt to overwrite protected global 'EntityApi'
Adding, replacing, or removing a field EntityApi.spawn = nil attempt to modify a read-only API table
Mutating a nested table MyApi.sub.fn = evil rejected — locking is deep
Table-library mutation table.insert(EntityApi, x), table.sort(EntityApi) rejected
Rebinding a metatable setmetatable(EntityApi, mt) rejected, on the API tables and on the globals table itself
Shadowing the name local EntityApi = {}, local function class() end, function f(WorldApi), for PlayerApi = 1, 3 do script is refused at load time, before any of it runs: attempt to declare ... shadowing protected global '...'

Shadowing is caught by scanning the script source (comments and string literals excluded) before the script is executed, because a local, a parameter, or a loop variable is pure lexical scoping and never reaches the globals table where the runtime guard lives. The whole script fails to load, so fix the name rather than expecting a partial load.

Reading is entirely unrestricted: you can call, index, and pass the API tables around freely. Only writes are blocked. If you need your own mutable helper, give it a different name.

Security

Important: The class() function allows loading any class from the server's classpath. This is intentional for maximum flexibility, but means:

  • Only allow trusted scripts on your server
  • Scripts can access all Bukkit/Paper APIs and any other libraries on the server
  • Scripts can potentially perform destructive operations if written maliciously

The API guardrails above stop scripts from breaking each other's APIs by accident or on purpose; they do not contain a malicious script, which retains full JVM access through class().

License

See LICENSE for details.

About

A Paper plugin that enables Lua scripting for Minecraft servers.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages