Loading…
Loading…
Select this tab to load…
Loading…
Loading…
Loading…
+ Add Minigame
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).
game <Name> {
settings { ... }
on [phase] <event>(<params>) { ... }
every <n> tick { ... }
fn <name>(<params>) { ... } // parsed but not yet callable — see Known limitations
}
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.
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 window | Lifecycle hook on entry |
|---|---|---|
| (omitted) → always | STARTING → CLOSING | on start() |
| prepare | STARTING → ACTIVE | — |
| active | ACTIVE → CLOSING | on 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.
| Event | Params | Notes |
|---|---|---|
| start | — | Lifecycle hook, fires once when the always adapter activates (STARTING). |
| active | — | Lifecycle hook, fires once when the active adapter activates (ACTIVE). |
| player_quit | player | |
| player_damage | player, cause | cause is a string, e.g. "LAVA", "FALL", "ENTITY_ATTACK". |
| block_break | player | |
| food_change | player, level | level is a number 0–20. |
| item_drop | player | |
| item_pickup | player | |
| player_move | player | Fires every tick the player moves — keep handlers cheap. |
| player_death | player, killer | killer is null if not killed by another player (fall, void, environment, ...). |
| player_interact | player, is_right_click, item_material, face_location | item_material is the main-hand item's material name string. face_location is null unless a block was clicked. |
| player_burn | player, fire_duration | |
| trident_shoot | player | |
| trident_hit | trident, hit_player | hit_player is null if the trident hit a block, not a player. |
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.
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.
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.
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).
| Method | Returns | Description |
|---|---|---|
| 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 null | A named location from the map config. |
| locations(prefix) | list of Location | All map-config locations whose key starts with prefix. |
| quitted_player(player) | — | Marks a player as having quit (vs. eliminated). |
| is_active(player) | bool | Whether 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 null | Team 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) | bool | Places a block only if the target is air. |
| set_block(location, material) | — | Unconditionally sets a block. |
| active_count() | number | Also available as the game.active_count property. |
| state() | string | Also 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) | number | Reads a per-map custom int property. |
| string_conf(key) | string or null | Reads a per-map custom string property. |
| bool_conf(key) | bool | Reads a per-map custom bool property. |
| destroy_area(location, radius) | — | Destroys blocks in a radius. |
| elapsed_seconds() | number | Seconds 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) | number | Reads 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) | bool | percent 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). |
| Method | Returns | Description |
|---|---|---|
| heal() | — | |
| set_velocity(x, y, z) | — | |
| clear_inventory() | — | |
| damage(amount) | — | |
| 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 | |
| name() | string | |
| location() | Location | |
| block() | string | Material name of the block the player is standing in. |
player also exposes .name, .on_ground, .flying
as plain properties (no parentheses) — mostly equivalent to the method forms above.
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 — the offset location's x/y/z
mirror the origin, only block() reflects 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.
- 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.
- 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.
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)
}
}
+ Add Map
Loading…
Loading…
Loading…
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
Loading…
Optional announcement shown at the top of the public store page (e.g. a sale, an event).
These drive both the public store page and checkout — no code deploy needed to add/edit/retire one.
Loading…
+ Add / Edit Product
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…
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…
Maintenance / Cleanup
Loading…
Select a table.
Loading…
Manage in-game /verify TOTP for staff members.
Loading…