Skip to content

swoftc · OCaml compiler · JVM runtime

The Minecraft scripting language that catches your bugs before the server boots.

Write commands, events, GUIs and scoreboards in readable English-flavored syntax. A flow-sensitive typechecker proves every maybe-missing value is handled — at compile time, with the error pointing at your line.

warp.sw
swoftlang
command "warp" {
    description: "Send someone to spawn"

    arguments {
        who: optional<Player>
    }

    execute async {
        set target to args.who
        send "<gray>Warping ${target.name} in 3 seconds..." to target
        wait 3 seconds
        teleport target to location(0.5, 64.0, 0.5)
        send "<green>Welcome to spawn, ${target.name}!" to target
    }
}

swoftc check warp.sw — real compiler output

txt
warp.sw:10:14: error: this value is optional<Player> and may be missing; check it with 'if ... exists' or provide a fallback with 'otherwise'
        send "<gray>Warping ${target.name} in 3 seconds..." to target
             ^

+ 7 more errors downstream of the same missing value

who is an optional argument — the caller may omit it. The broken variant uses it as if it were always there, so swoftc check refuses to compile until the miss has a written-down answer. One otherwise sender later, the type is plain Player and the script ships. That's the whole philosophy — the Options guide is Step 07.

01

Skript, translated function by function

Everything you already know maps over — events, persistent variables, waits. Read your Skript on the left, its SwoftLang on the right, aligned pair by pair.

Skript-to-SwoftLang mappings run through the whole guide →

join event
skript
on join:
    send "&aWelcome, %player%!" to player
    broadcast "&7%player% joined"
swoftlang
event PlayerJoin {
    execute {
        send "<green>Welcome, ${event.player.name}!" to event.player
        send "<gray>${event.player.name} joined" to all
    }
}
kill counter
skript
on death of monster:
    attacker is a player
    add 1 to {kills::%uuid of attacker%}
    send "&e%{kills::%uuid of attacker%}% kills" to attacker
swoftlang
persistent kills for Player: Integer = 0

event MobDeath {
    execute {
        if event.killer exists {
            set kills for event.killer to kills for event.killer + 1
            send "<yellow>${kills for event.killer} kills" to event.killer
        }
    }
}
Why it maps this way

event.killer is optional<Player> — mobs die to lava too. Skript hands you an attacker that might silently be nothing; SwoftLang won't compile until the exists check is there. The persistent … for Player declaration replaces the {kills::%uuid%} list-variable idiom: typed, defaulted, and saved across restarts.

wait without blocking
skript
command /crate:
    trigger:
        send "&7Opening your crate..." to player
        wait 3 seconds
        send "&aYou won a diamond!" to player
swoftlang
command "crate" {
    execute async {
        send "<gray>Opening your crate..." to sender
        wait 3 seconds
        send "<green>You won a diamond!" to sender
    }
}
Why it maps this way

Same shape, different guarantee: execute async parks a virtual thread, never the tick thread — and moving that wait into a sync handler is a compile error, not a laggy server.

02

The compiler knows your property table

Every event.player.… access is checked against the real Minestom-backed property table — with typo suggestions and read-only enforcement, before the server ever boots.

Properties guide →

swoftlang
event PlayerJoin {
    execute {
        send "hi ${event.player.nmae}" to event.player
    }
}

swoftc check welcome.sw

txt
welcome.sw:3:14: error: unknown property 'nmae' on Player; did you mean 'name'?
        send "hi ${event.player.nmae}" to event.player
             ^

03

Async that reads top to bottom

No callbacks, no schedulers, no runTaskLater. wait parks a virtual thread; spawn fires and forgets; async functions return values you can just add.

Async guide →

swoftlang
command "crate" {
    description: "Open a timed reward crate"

    execute async {
        send "<gray>Opening your crate..." to sender
        wait 3 seconds
        send "<green>You won a diamond!" to sender
        spawn announce(sender.name)
    }
}

async function announce(name: String) {
    wait 1 seconds
    send "<gold>${name} just opened a crate" to all
}

04

GUIs are declarations, not inventory-click plumbing

Describe the inventory — rows, slots, items, click handlers — and the runtime renders it, diffs it, and re-renders on state change.

GUI reference →

swoftlang
gui "menu" {
    rows: 3
    title: "Server Menu"

    slot 13 {
        item { material: "COMPASS", name: "<aqua>Warp home" }
        on_click {
            send "<gray>Warping..." to player
            close gui for player
        }
    }
}

command "menu" {
    execute {
        open gui "menu" to sender
    }
}

05

State that survives restarts

One keyword. Typed, defaulted, keyed by UUID when you say for Player, flushed write-behind to files, SQLite, MySQL or MongoDB. Reads are never missing.

Persistence guide →

swoftlang
persistent visits for Player: Integer = 0

event PlayerJoin {
    execute {
        set visits for event.player to visits for event.player + 1
        send "<gray>Visit #${visits for event.player}" to event.player
    }
}

The guide is a numbered course — 16 steps, one concept each, ending in something runnable.

Step 01 · Setup

Or go deeper: full Skript plugins ported line by line and the standard library addons.