Porting a Bukkit, Spigot, Paper or Folia plugin by hand
Status: Implemented as a mapping to the API that exists; rows marked planned name CLight features that aren’t in the game yet.
Celestial’s server does not run, convert or translate Java plugins (.jar files for Bukkit, Spigot, Paper or Folia), and there is no conversion tool. This guide maps plugin concepts to CLight so you can rewrite your own plugin. Read Porting existing mods to CLight: overview first.
A CLight server plugin is an ordinary CLight mod with "side": "server" and "sync": "server_only". Vanilla clients can join, just as with Paper. Lua is the natural language for most plugins: no build step, hot reload in dev mode, sandboxed, and fast enough for plugin logic (Server plugins, A server plugin (vanilla clients welcome)).
Project and metadata
Bukkit / Paper
CLight
plugin.yml / paper-plugin.yml
clight.mod.json with side: server, sync: server_only
name
id (lowercase) and name
main (the plugin class)
runtime.entry (Lua script) or the code entry point
api-version
api (the CLight API major) and game (a game version range)
depend, softdepend, loadbefore
depends, optional, load.before
commands: section
Registered in code as command trees
permissions: section
Command permission levels and names on the nodes (registered permission nodes planned)
plugins/ folder
mods/ on the server
Listeners and events
Bukkit / Paper
CLight
@EventHandler on a Listener
cl.on("event.name", fn, opts)
EventPriority.LOWEST … HIGHEST, MONITOR
priority = "first" … "last", "monitor" (note: lowest runs first in both)
ignoreCancelled = true
the default (cancelled events skip later handlers); receive_cancelled = true to see them
PlayerJoinEvent, PlayerQuitEvent
player.join, player.leave
AsyncPlayerChatEvent / AsyncChatEvent
player.chat (message mutable, cancellable)
PlayerCommandPreprocessEvent
player.command
BlockBreakEvent, BlockPlaceEvent
block.break (xp, drop_items mutable), block.place
PlayerInteractEvent
player.interact_block, player.interact_item
EntityDamageEvent, EntityDeathEvent
entity.damage (amount mutable), entity.death
PlayerRespawnEvent
player.respawn
EntityDamageByEntityEvent (attacker)
entity.damage (attacker), player.attack_entity
InventoryClickEvent on your own GUIs
the menu’s on_click
Move, teleport, world change, inventory open/close, async pre-login
Config: local config = cl.config.open("server"), config:define_i32("radius", 30, 0, 1000).
Subscribe to block.break and block.place with priority = "first". In the handler: if event.world:dimension() == "minecraft:overworld" and the position is within the radius, and the player isn’t an operator (keep your own list, or check in a command), event:cancel().
Register /spawnprot radius <n> with an integer argument and requires_permission(2), setting the config value and saving it.
Test with a vanilla client: the cancelled break puts the block back for the client, exactly as vanilla does in adventure mode.