// News

Loading…

// Server Instances
Whitelist When enabled, only listed players can join. Others see "Coming Soon!"
Server List MOTD
Line 2 there is automatic — the private server's current minigame or map name.

Loading…

// Instance History

Select this tab to load…

// Users

Loading…

// Your Profile

Loading…

// Minigames

Loading…

+ Add Minigame
// NolensiaScript Docs

NolensiaScript (.ns) is the DSL for writing minigames without Java — parsed once, tree-walked, no compile step, editable straight from Minigames → Add/Edit (type ns). Source of truth: JavaNolensia/NolensiaScriptRuntime/ and nolensiascript/README.md in the repo. Live examples: nolensiascript/*.ns (ForkSpleef, Duels, TowerWars, KingOfTheTowers, ThePirates, OneInTheQuiver).

Top-level structure
game <Name> {
    settings { ... }

    on [phase] <event>(<params>) { ... }
    every <n> tick { ... }
    fn <name>(<params>) { ... }    // parsed but not yet callable — see Known limitations
}
Settings
settings {
    difficulty: hard        // peaceful | easy | normal | hard (default: normal)
    nat_regen: false        // natural health regen (default: true)
    mob_spawning: false     // default: true
    gamemode: survival      // survival | adventure (default: adventure)
    block_breaking: false   // default: true
    block_placing: false    // default: true
    saturation: 20          // permanent ambient SATURATION effect, amplifier as int
    min_players: 2          // default: 1
    max_players: 8          // default: 100
    team_mode: false        // default: false
    debug: true             // logs every dispatched event + cancel state (default: false)
}

Unquoted words in a settings value (hard, survival, ...) are read as plain strings — don't quote them.

Handlers — on [phase] event(params) { }

phase is optional and, when present, must be followed by another identifier (the event name) — otherwise the word is parsed as the event name itself. Three phases exist, matching the Java GameConfigurations split:

Phase (default: always)Active windowLifecycle hook on entry
(omitted) → alwaysSTARTING → CLOSINGon start()
prepareSTARTING → ACTIVE
activeACTIVE → CLOSINGon active()

A handler for the same event name can be declared once per phase — e.g. on prepare player_death(player) and on active player_death(player) are independent handlers that never both fire for the same death. Call cancel (bare keyword, or cancel()) anywhere in a handler body to cancel the underlying Bukkit event — it does not stop executing the rest of the handler body, it just flags the result.

Events
EventParamsNotes
startLifecycle hook, fires once when the always adapter activates (STARTING).
activeLifecycle hook, fires once when the active adapter activates (ACTIVE).
player_quitplayer
player_damageplayer, causecause is a string, e.g. "LAVA", "FALL", "ENTITY_ATTACK".
block_breakplayer
food_changeplayer, levellevel is a number 0–20.
item_dropplayer
item_pickupplayer
player_moveplayerFires every tick the player moves — keep handlers cheap.
player_deathplayer, killerkiller is null if not killed by another player (fall, void, environment, ...).
player_interactplayer, is_right_click, item_material, face_locationitem_material is the main-hand item's material name string. face_location is null unless a block was clicked.
player_burnplayer, fire_duration
trident_shootplayer
trident_hittrident, hit_playerhit_player is null if the trident hit a block, not a player.
arrow_hitshooter, hit_player, arrowshooter/hit_player are null if not applicable (dispenser-fired / hit a block). arrow is appended last, not first — kept backwards-compatible with scripts declaring just (shooter, hit_player).
Timers — every <n> tick { }
every 20 tick {
    game.message("tick!")
}

Runs every n ticks for the lifetime of the match (registered once, cancelled automatically when the match ends). Each timer body's exceptions are isolated — one broken every block logs an error but never stops other timers or itself on future ticks. tick/ticks are interchangeable and optional after the number, but the block itself is required.

Statements
let x = 5
if x > 3 { game.message("big") } else { game.message("small") }
for p in active_players { p.heal() }
cancel
return
return some_value

let declares a variable in the current block scope (shadows outer scopes; for/if bodies get their own child scope). for x in <list-expr> requires the iterable to evaluate to a list (players, active_players, or game.locations(prefix)) — anything else throws. There's no numeric range loop and no list literals — bulk operations like game.trident_rain or game.fill_disk exist precisely because that plumbing doesn't exist at the script level; reach for a game.* bulk helper before trying to hand-roll a loop.

Operators & comments

Arithmetic: + - * / (numbers only, except + also does string concatenation whenever either side is a string). Comparison: == != < > <= >=. Logical: and or not (short-circuiting). Unary minus: -x. Comments are // line comment only — no block comments.

Built-in globals

Rebuilt before every single event dispatch, so they're always fresh: game (the running match), spawn (a Location), players (everyone currently in the match, including eliminated/spectating), active_players (players still alive/active).

game.* methods
MethodReturnsDescription
message(text)Broadcasts to everyone in the match.
sound(name) / sound(name, pitch, volume)Plays a sound to everyone.
remove(player)Removes a player from the active set (eliminates them).
set_winner(player)Marks the winner and ends the match.
location(name)Location or nullA named location from the map config.
locations(prefix)list of LocationAll map-config locations whose key starts with prefix.
quitted_player(player)Marks a player as having quit (vs. eliminated).
is_active(player)boolWhether a player is still in the active set.
apply_kit(player, index)Applies the map's assigned kit at index to the player.
spawn_entity(player, location, type, opts)Spawns an entity from a spawn-egg-style config.
player_team(player)string or nullTeam name, only meaningful when team_mode: true.
delay_item(player, material, ticks)Gives one material item after a delay.
place_block_if_air(location, material)boolPlaces a block only if the target is air.
set_block(location, material)Unconditionally sets a block.
drop_item(location, material) / drop_item(location, material, amount)Drops a real, pickable item entity on the ground — unlike player.add_item (straight into an inventory), e.g. a missed-shot arrow's replacement lying where it landed.
active_count()numberAlso available as the game.active_count property.
state()stringAlso available as the game.state property ("starting", "active", ...).
delay_respawn(player, ticks)Teleports the player back to spawn and sets ADVENTURE mode after a delay.
fill_disk(location, radius, y_offset, material)Fills a flat disk.
clear_disk(location, radius, y_offset, material)Clears (air) a flat disk.
int_conf(key) / int_conf(key, default)numberReads a per-map custom int property.
string_conf(key)string or nullReads a per-map custom string property.
bool_conf(key)boolReads a per-map custom bool property.
destroy_area(location, radius)Destroys blocks in a radius.
elapsed_seconds()numberSeconds since the match went ACTIVE.
add_stat(player, name, amount)Adds to a per-player numeric counter (e.g. a kill count). Same accumulator the orchestrator reads for custom EXP bonuses.
get_stat(player, name)numberReads a per-player counter, defaulting to 0.
has_flag(name) / set_flag(name)bool / —One-shot boolean flags — the idiomatic way to gate a "fire once" block inside a timer (see ForkSpleef's sudden-death warning).
spawn_trident(location, seek)Drops a falling trident; homes in on location if seek is true.
chance(percent)boolpercent in [0, 100]; game.chance(50) is a coin flip.
random_active_player()Player or null
trident_rain(count)Drops count tridents above random active players (skips liquid tiles, each independently seeking or not).
assign_indices(prefix, stat)Assigns every active player a unique 0-based index into a named location-prefix list, stored as stat. Enumerate-and-assign lives here since the language has no loop counter.
location_at(prefix, index)Location or nullThe Nth location (0-based) from a named prefix list — pairs with assign_indices.
delay_teleport(player, location, ticks)Like delay_respawn, but to an arbitrary location instead of the map's shared spawn.
build_walls(location, radius, height, y_offset, material)Builds a hollow box perimeter (walls only, no floor/ceiling) — pair with fill_disk (same y_offset) for an enclosed pod.
replace_disk(location, radius, y_offset, match_material, replacement)Like clear_disk, but replaces matching blocks with replacement instead of always AIR — e.g. reverting a build-phase floor back to WATER.
clear_walls(location, radius, height, y_offset, match_material)Reverts exactly what build_walls (same radius/height/y_offset) put up, back to air.
starting_seconds_remaining()numberSeconds left in the STARTING countdown (settings.starting_seconds) — drive your own sidebar/milestone feedback for a build phase instead of the engine's default per-second chat line.
ready_up(floor_seconds)Fast-forwards the STARTING countdown down to floor_seconds — never lengthens it, no-op once already at/below the floor.
mark_ready(player, floor_seconds)Marks one player ready; only once EVERY player has done so does it actually fast-forward the countdown (see ready_up) and broadcast. Tracked in Java, not a script loop — see Known limitations.
update_stat_sidebar(title, stat_key)Renders a live "Name: N" leaderboard sidebar (sorted descending by stat_key) to everyone in the match. Re-render on every change.
assign_ring_positions(center, radius, y, prefix)Spreads every player (not just active ones — runs during the build phase, before the active roster exists) evenly around a ring centered on center at height y. No hand-placed map locations needed. Stored as stats; read back with stat_location.
stat_location(player, prefix)Location or nullReconstructs a location previously stored by assign_ring_positions (or anything else writing <prefix>_x/_y/_z stats).
capture_raft(owner, chest_anchor, radius_xz, height_down, height_up)Captures whatever's currently built around chest_anchor as owner's moveable raft — call once their build phase ends. See the Rafts section below.
move_raft(owner, dx, dz)Moves owner's captured raft by one block (dx/dz each -1, 0, or 1), carrying along anyone standing on it. No-op if they have no raft.
raft_chest_location(owner)Location or nullowner's raft's current (post-movement) chest location.
Rafts (capture_raft / move_raft)

A captured raft turns every non-air block in the scanned box into a real, moveable entity rather than tracking world blocks directly. Regular hull/wall blocks become invisible Shulkers (real collision, so players can stand and walk on the raft) paired with a BlockDisplay rendering the original block — destroyed by combat via a hit counter (3 hits). A block placed matching the chest material (CHEST) or the helm material (OAK_FENCE_GATE, configured in Java as MinigameHandler.HELM_MATERIAL) instead becomes an invisible ArmorStand — fully invulnerable to combat, no walking collision, and (the helm only) mountable, so a player can right-click it to sit down and steer. The only way to lose a chest/helm is to destroy whatever raft block was directly supporting it — once unsupported it accelerates downward and is destroyed once it's sunk a bit into real water (not the instant it touches the surface). A chest/helm built flush into the raft's own floor row is always considered supported (nothing was ever captured below the floor to compare against) — build it one block up on a raised post if you want it to actually be sinkable. All of this (hit-counting, steering, fall/sink) is wired entirely in Java (MinigameHandler/RaftChestHitListener), not exposed as separate script builtins beyond capture_raft/move_raft/raft_chest_location.

player.* methods
MethodReturnsDescription
heal()
set_velocity(x, y, z)
clear_inventory()
damage(amount)
damage_from(amount, attacker)Like damage, but attributes the hit to attacker so player_death's killer param resolves correctly even if this is the fatal hit.
set_saturation(level)
throw_tnt(fuseTicks)
reduce_hand()Reduces the main-hand item stack by one.
add_item(material) / add_item(material, amount)
add_enchanted_item(material, amount, unbreakable, "NAME:LEVEL,NAME:LEVEL")Enchant string is comma-separated NAME:LEVEL pairs.
teleport(location)
set_gamemode(mode)"survival" | "creative" | "spectator", anything else → adventure.
set_food(level)Alias of set_saturation.
send(text)Sends a chat message to just this player.
effect(type, ticks, amplifier)Applies an ambient potion effect.
is_on_ground()bool
is_flying()bool
set_allow_flight(allow)Grants/revokes flight capability outright (a real flight toggle, same as creative — not just "not falling"). Needed for anything standing on an entity-based structure (see Rafts) rather than a real block, since Paper's ground check doesn't count entity collision and will eventually kick the player for "floating". Trade-off: also lets the player double-jump into real flight in survival.
facing()string4-way cardinal snap (NORTH/SOUTH/EAST/WEST) from yaw — NOT Bukkit's own 8-way getFacing().
name()string
location()Location
block()stringMaterial name of the block the player is standing in.

player also exposes .name, .on_ground, .flying, .facing as plain properties (no parentheses) — mostly equivalent to the method forms above.

Location.* and trident.* methods

Location: x(), y(), z(), block() (material name of the block there), and offset(dx, dy, dz) (returns a new Location shifted by a relative block offset — x(), y(), z(), and block() all reflect the offset position). .x, .y, .z are also available as properties. Trident (inside trident_hit only): location() and remove() — also exposed as the .location property. Arrow (inside arrow_hit only) is the same shape: location() and remove().

Known limitations
  • fn is parsed but not wired up. Declarations are collected by the interpreter but nothing ever looks them up — there's also no call syntax for a bare identifier (myFn(x)) in the grammar, only <object>.<method>(...). Don't rely on fn yet; repeat logic inline or push it into a new built-in if it needs to be shared.
  • No numeric ranges or list literals. Bulk operations are built-in game.* helpers rather than expressible as a script-level loop.
  • A for loop can't accumulate state into an outer-scope variable. Each iteration's body runs in its own fresh child Environment, so a running total/flag declared before the loop is never visible to mutate from inside it. Anything that needs "have ALL of these happened yet" (e.g. mark_ready's "is everyone ready") has to be tracked in Java instead.
  • Each for iteration's exceptions are isolated the same way every timers are — one bad iteration is logged and skipped, it doesn't abort the rest of the loop or the handler.
  • return outside of a handler is a no-op that just unwinds the current block — there is no user-callable function to return a value to.
  • Comparing a number to anything other than an identical number/string/boolean via ==/!= always returns false — no numeric-vs-string coercion.
  • A script error thrown inside a handler is logged (with a stack trace if debug: true) and the event resolves as not cancelled — it does not crash the match.
Full example — ForkSpleef.ns
game ForkSpleef {

    settings {
        debug: true
    }

    on start() {
        // Nothing to set up during prepare phase
    }

    on active() {
        for p in active_players {
            p.add_item("TRIDENT")
        }
    }

    every 20 tick {
        if game.elapsed_seconds() >= 150 and not game.has_flag("sudden_death_warned") {
            game.set_flag("sudden_death_warned")
            game.message("Sudden death in 30 seconds!")
        }
        if game.elapsed_seconds() >= 180 {
            if not game.has_flag("sudden_death_started") {
                game.set_flag("sudden_death_started")
                game.message("SUDDEN DEATH!")
                game.sound("entity.lightning_bolt.thunder", 1.0, 1.0)
            }
            game.trident_rain(2)
        }
    }

    on prepare player_death(player) {
        cancel
        game.delay_respawn(player, 1)
    }

    on active player_death(player) {
        cancel
        player.set_gamemode("spectator")
        if game.is_active(player) {
            game.message(player.name() + " has been ELIMINATED!")
            game.remove(player)
            game.sound("entity.player.attack.crit", 1.0, 1.0)
        }
    }

    on active trident_shoot(player) {
        game.delay_item(player, "TRIDENT", 20)
    }

    on active item_pickup(player) { cancel }

    on active trident_hit(trident, hit_player) {
        if hit_player != null {
            cancel
            game.destroy_area(hit_player.location(), 1)
        } else {
            game.destroy_area(trident.location(), 1)
        }
        trident.remove()
    }

    on player_move(player) {
        let block = player.block()
        if block == "WATER" or block == "BUBBLE_COLUMN" {
            player.damage(9999)
        }
    }

    on player_damage(player, cause) {
        if cause == "LAVA" { player.damage(9999); cancel }
        if cause == "FALL"          { cancel }
        if cause == "ENTITY_ATTACK" { cancel }
    }

    on item_drop(player)        { cancel }
    on block_break(player)      { cancel }
    on item_pickup(player)      { cancel }
    on player_burn(player, dur) { cancel }

    on food_change(player, level) {
        player.set_saturation(0)
        cancel
    }

    on player_quit(player) {
        player.set_gamemode("spectator")
        game.message(player.name() + " has been removed from the game!")
        game.quitted_player(player)
    }
}
// Custom Maps
+ Add Map
Map Zip
No file chosen
Resource Pack (optional)
No file chosen
Shared Stats

Loading…

// Events

Loading…

// Punishments

Loading…

// Cosmetics Catalog

Metadata mirror of what's defined in LobbyPaper's Title/Pet/Morph code — bookkeeping only, not read by the game servers. Grant/revoke a specific player's cosmetics from Users → Edit.

Loading…

+ Add / Edit Cosmetic
// Recent Trades

Loading…

// Store Banner

Optional announcement shown at the top of the public store page (e.g. a sale, an event).

// Store Products

These drive both the public store page and checkout — no code deploy needed to add/edit/retire one.

Loading…

+ Add / Edit Product
// Store Purchases

Every completed store checkout, recorded when the Stripe webhook fires. If a purchase doesn't show up here after a successful checkout, the webhook isn't reaching the server — check /var/log/nolensia-store.log and the endpoint's config in the Stripe Dashboard.

Loading…

// Bug Reports

Loading…

// Account Settings

Loading…

Change password
Two-factor authentication

Loading…

// Panel Users

Loading…

Add panel user
// File Manager

Loading…

// Maps
World Files (.slime)

Loading…

Register World

Loading…

// Lobby
Loading…
Plugins
Config (server.properties)
// Database
Maintenance / Cleanup
Clear expired sessions
Purge old instance logs days
Delete inactive players days inactive

Loading…

Select a table.

// Performance

Loading…

// Staff 2FA

Manage in-game /verify TOTP for staff members.

Set up for player
Staff roster

Loading…