Scripts
Katton organizes your Kotlin code into Script Packs. A script pack is simply a folder containing a manifest.json and one or more .kt files. Unlike traditional mods, script packs are side-agnostic — the same pack can serve both server and client, and everything is hot-reloadable with /katton reload.
Script Packs
Directory Layout
Script packs live inside a directory named kattonpacks, which Katton scans automatically:
Global — shared across all worlds, survives world deletion:
<gameDir>/kattonpacks/<packName>/manifest.json<gameDir>/kattonpacks/<packName>/**/*.ktWorld — per-world, created automatically when you first reload:
<worldDir>/kattonpacks/<packName>/manifest.json<worldDir>/kattonpacks/<packName>/**/*.kt
- Global packs load once when the mod starts and are shared across all worlds.
- World packs load per save — ideal for map-specific scripts.
Inside a pack, .kt files can be nested in any subdirectory structure. Katton walks the entire tree. Your entrypoint can be at the root, in src/main/, in test/ — anywhere.
Manifest (manifest.json)
Every pack needs a manifest.json at its root. All fields are optional — Katton fills in sensible defaults for anything you omit:
{
"name": "My Awesome Pack",
"id": "my_pack",
"version": "1.0.0",
"authors": ["YourName"],
"description": "A cool Katton script pack!",
"enabled": true
}| Field | Default | Description |
|---|---|---|
id | folder or jar filename | Unique pack identifier — used in sync, logs, and commands |
name | same as id | Human-readable display name (shown in the pack UI) |
version | "unknown" | Semantic version string |
description | "" | What your pack does |
authors | [] | Array of author names |
enabled | true | true = loaded on reload, false = skipped |
NOTE
There is no targets.server or targets.client field. Katton determines whether a script runs on the server or client purely from the annotations on its entrypoint functions (see below).
State File (.kattonpack.state.json)
When you toggle a pack on/off from the in-game UI (press K), Katton writes a local state file:
{ "enabled": false }This overrides the enabled field in manifest.json. Delete the state file to reset back to the manifest default.
Entrypoints
Scripts are side-agnostic — a single .kt file can contain both server and client logic. Which environment executes which function is decided by two simple annotations:
import top.katton.api.ServerScriptEntrypoint // ← runs on the server
import top.katton.api.ClientScriptEntrypoint // ← runs on the clientBoth annotations mark top-level, no-argument functions. That's the only requirement:
@ServerScriptEntrypoint
fun initMyCommands() {
// This only runs on the server
}@ClientScriptEntrypoint
fun initMyHUD() {
// This only runs on the client
}You can have as many entrypoint functions as you want in a single file — each one is discovered and invoked independently during reload.
CAUTION
Katton does not prevent you from calling server-only APIs from a @ClientScriptEntrypoint function (or vice versa). Doing so will likely crash that side. Keep your server logic and client logic in separate entrypoint functions.
Hot Reload
Run /katton reload to reload all scripts without restarting the game. This is the heart of Katton's development loop:
- Re-scans all enabled packs in global and world scopes
- Clears event handlers, injections, and registry ownership
- Re-compiles all source packs together, then loads JAR packs
- Discovers and invokes all
@ServerScriptEntrypoint/@ClientScriptEntrypointfunctions - Shows a visual progress bar at the top of the screen (message + percentage + green bar)
You can also trigger reloads indirectly:
/reload(vanilla) → triggers server-side Katton reloadF3 + T(reload resources) → triggers client-side Katton reload- Pack UI → press K, click Reload — triggers both sides
Client Scripts
Client-side scripts are great for HUD overlays, custom renderers, UI interactions, and anything that needs access to Minecraft.getInstance() or rendering APIs.
import top.katton.api.HudRenderLayer
import top.katton.api.clientFps
import top.katton.api.clientPos
import top.katton.api.clientScreenName
import top.katton.api.clientTell
import top.katton.api.drawHudText
import top.katton.api.drawHudTexture
import top.katton.api.fillHudRect
import top.katton.api.once
import top.katton.api.registerHudRenderer
import top.katton.api.unregisterHudRenderer
@ClientScriptEntrypoint
fun hudRenderTestMain() {
unregisterHudRenderer("katton:test:hud")
registerHudRenderer("katton:test:hud", HudRenderLayer.FOREGROUND, 20) { ctx ->
fillHudRect(ctx, 6, 6, 258, 64, 0xAA000000.toInt())
fillHudRect(ctx, 8, 8, 256, 62, 0x66002244)
drawHudText(ctx, "Katton HUD Render Test", 14, 14, 0xFFE8F1FF.toInt(), true)
drawHudText(ctx, "FPS: ${clientFps() ?: -1}", 14, 28, 0xFF9BD5FF.toInt(), false)
drawHudText(ctx, "Screen: ${clientScreenName() ?: "In-World"}", 14, 40, 0xFFB5FFC5.toInt(), false)
val p = clientPos()
if (p != null) {
drawHudText(ctx, "Pos: %.2f, %.2f, %.2f".format(p.x, p.y, p.z), 14, 52, 0xFFFFD38A.toInt(), false)
}
}
clientTell("[Katton] HUD render test script loaded")
}Client scripts can live in the same pack as server scripts (they just need @ClientScriptEntrypoint). On multiplayer servers, client packs are automatically synced from the server to <gameDir>/serverpacks/.
Server Scripts
Server-side scripts handle game logic: commands, events, registries, world manipulation, and datapack integration.
ServerPlayerEvent.onPlayerJoin += join@ fun(arg: PlayerArg) {
val player = arg.player
val firstSeen = once(key = "welcome:${player.uuid}", namespace = "event_player_lifecycle_demo") {}
if (firstSeen) {
tell(player, "[event-demo] Welcome to the server! Enjoy your stay! ")
} else {
tell(player, "[event-demo] Welcome back!")
}
}In this example, we subscribe to the onPlayerJoin event to send a greeting. The once API checks whether the player is joining for the first time, so we can tailor the message accordingly.
Advanced: Server→Client Sync
When a client connects to a multiplayer server, Katton automatically syncs server-authoritative script packs to the client:
- Server sends a hash list (
ScriptPackHashListPacket) — mapping each pack's sync ID to its SHA-256 - Client compares hashes against its local cache at
<gameDir>/serverpacks/<bucket>/ - Client requests any missing or mismatched packs (
ScriptPackRequestPacket) - Server sends full bundles (
ScriptPackBundlePacket) — manifest + all script files - Client persists to disk and executes before registry validation
This ensures every player runs the exact same scripts as the server — no manual installation, no version mismatches. The sync protocol is fully automatic and requires zero configuration from you.
Legacy Locations (Still Supported)
If you have existing setups using the old conventions, they still work:
- Datapacks:
data/<namespace>/scripts/— server scripts - Resource packs:
assets/<namespace>/client_scripts/— client scripts
Katton scans these locations alongside kattonpacks/ during reload. However, kattonpacks/ is the recommended format — it supports manifest files, the in-game pack UI, toggle switches, and the full reload lifecycle. Old-format scripts silently use default settings.
