Advanced Features & Mechanics

Beyond basic scripting, SScript has powerful advanced features for complex logic, error handling, and optimization.


Error Handling with Try-Catch

Basic Syntax

try:
    // Code that might fail
    result = http_get("https://api.example.com/data")
    data = result.json
    log data.score
catch err:
    // Handle error
    log "Error occurred: " + err
end

Common Errors to Catch

try:
    // Invalid file path
    content = file_read("nonexistent.txt")
catch err:
    log "File not found: " + err
end

try:
    // Network timeout
    resp = http_get("https://slow-server.com")
catch err:
    log "HTTP error: " + err
end

try:
    // JSON parse error
    obj = json_parse("invalid json")
catch err:
    log "JSON parse failed: " + err
end

try:
    // Division by zero or type mismatch
    result = 10 / 0
catch err:
    log "Math error: " + err
end

Error Message Details

When an error is caught, err contains:

  • Error type (FileNotFound, HttpTimeout, JsonParseError, etc.)
  • Message (human-readable description)
  • Line number (where error occurred)

Example:

try:
    run "invalid command syntax"
catch err:
    log "Error: " + err  // "Error: CommandSyntaxException at line 42"
end

Player Selectors

SScript executes Minecraft selectors at runtime to find players.

Supported Selectors

// All online players
players = get_targets("@a")

// All online players within 100 blocks
nearby = get_targets("@a[distance=..100]")

// Players with specific tag
verified = get_targets("@a[tag=verified]")

// Player with lowest score
loser = get_target("@a[sort=nearest,limit=1]")

Selector Properties

Selector Effect Example
distance Range in blocks @a[distance=0..50], @a[distance=..100]
tag Has/no tag @a[tag=admin], @a[tag=!banned]
scores Objective score @a[scores={kills=5..}]
gamemode Survival/Creative/etc @a[gamemode=survival]
limit Max results @a[limit=5]
sort nearest/random/furthest @a[sort=nearest]
name Player name @a[name=Steve]
level Experience level @a[level=10..]

Dynamic Selectors

func find_admins():
    admins = get_targets("@a[tag=admin]")
    log "Found " + str(len(admins)) + " admins"
    return admins
end

on player_join(player):
    nearby = get_targets("@a[distance=0..50,gamemode=survival]")
    for p in nearby:
        tellraw(p.name, player.name + " is nearby!")
    end
end

Player Object Structure & NBT Data

Player objects contain Minecraft NBT data extracted at runtime:

Basic Properties

player.name           // "Steve"
player.uuid           // "a1b2c3d4-..." (unique ID)
player.health         // 20.0 (0-20)
player.max_health     // 20.0
player.food_level     // 10 (0-20)
player.saturation     // 5.2 (decimal)
player.x_pos          // 100.5 (float)
player.y_pos          // 64.2
player.z_pos          // -50.1
player.dimension      // "minecraft:overworld"
player.gamemode       // "survival", "creative", "adventure", "spectator"
player.level          // 42 (XP level)
player.experience     // 0.8 (0.0-1.0, decimal part)

Advanced Properties

player.on_ground      // true/false
player.flying         // true/false
player.sneaking       // true/false
player.sprinting      // true/false
player.swimming       // true/false
player.fall_distance  // 3.2 (blocks fallen)
player.fire           // 0 (ticks on fire)
player.air            // 300 (remaining air while underwater)
player.pose           // "standing", "swimming", "falling", etc

Inventory Example

on player_join(player):
    health = player.health
    level = player.level
    
    if health < 5:
        log player.name + " has low health: " + str(health)
    end
    
    if level >= 30:
        tag_add(player.name, "high_level")
    end
end

HTTP Headers & Custom Requests

Basic GET/POST (Auto Headers)

// GET request - auto Content-Type: application/x-www-form-urlencoded
resp = http_get("https://api.example.com/data")

// POST request - auto Content-Type: application/json if body is JSON
resp = http_post("https://api.example.com/data", {"key": "value"})

Full Request with Custom Headers

response = http_request(
    "POST",
    "https://api.example.com/users",
    {
        "Authorization": "Bearer token123",
        "X-Custom-Header": "myvalue"
    },
    {"name": "Steve", "score": 100}
)

if response.ok:
    log "Request succeeded: " + str(response.status)
else
    log "Request failed: " + str(response.status)
end

Response Object

response.ok         // true if 200-299 status
response.status     // 200, 404, 500, etc.
response.text       // Raw response body as string
response.json       // Parsed JSON object (if JSON response)
response.headers    // Map of response headers

Example: API Integration

func fetch_player_stats(player_name):
    try:
        resp = http_get("https://api.example.com/players/" + player_name)
        if resp.ok:
            stats = resp.json
            return stats  // {"kills": 10, "deaths": 2, "wins": 5}
        else
            log "API error: " + str(resp.status)
            return null
        end
    catch err:
        log "Network error: " + err
        return null
    end
end

on player_join(player):
    stats = fetch_player_stats(player.name)
    if stats:
        tellraw(player.name, "Kills: " + str(stats.kills))
    end
end

List Methods

Lists/arrays support methods for manipulation:

.add(value) — Add Element

items = []
items.add("apple")
items.add("banana")
items.add("apple")

log len(items)  // 3

.remove(index) — Remove by Index

items = ["a", "b", "c"]
items.remove(1)      // Remove "b"

log items           // ["a", "c"]
log len(items)      // 2

Practical Example

func collect_online_staffs():
    all_players = get_targets("@a")
    staff_list = []
    
    for player in all_players:
        if has_tag(player.name, "staff"):
            staff_list.add(player.name)
        end
    end
    
    return staff_list
end

on player_break_block(player, block):
    staffs = collect_online_staffs()
    
    for staff_name in staffs:
        tellraw(staff_name, player.name + " broke " + block.id)
    end
end

Global Variables Persistence

Global variables are saved to sscripts/globals.json and persist across server restarts.

Automatic Save Behavior

  • Changes are debounced: saved at most every 2 seconds
  • Not saved immediately (to avoid I/O overhead)
  • Saved when server stops gracefully

Manual Save Triggers

set_global("important_data", {"version": 3})
// Will be flushed to disk within 2 seconds

// Immediate save (on next tick):
// Server shutdown triggers save

Example: Player Stats Persistence

func record_kill(player_name):
    current = num(get_global("kills_" + player_name))
    set_global("kills_" + player_name, current + 1)
    
    // Saved to disk automatically
end

on player_death(player, location):
    if location.killer:
        record_kill(location.killer)
    end
end

on server_start:
    // Load saved kill counts
    log "Loading persistent stats..."
    // (stats loaded from globals.json)
end

Performance Constraints & Limits

Execution Limits

Limit Value Consequence
Max running processes 500 Cannot spawn more (queue instead)
Processes spawned per tick 20 Max 20 new processes/tick (prevents lag spike)
Statements per tick 50 Process limited to 50 statements/tick (spread over multiple ticks)
Loop iterations 1,000,000 Infinite loops break (security limit)
While loop guard Enabled Prevents runaway loops
HTTP timeout 60 seconds Long requests abort after 60s
File I/O timeout 30 seconds File operations timeout

Memory Optimization

//  BAD: Creates 100k list at once
on server_start:
    huge_list = get_blocks(-1000, 0, -1000, 1000, 100, 1000)
end

//  GOOD: Process blocks in smaller chunks
on server_start:
    wait process_region_1()
    wait process_region_2()
    wait process_region_3()
end

func process_region_1():
    blocks = get_blocks(-1000, 0, -1000, 0, 100, 1000)
    log "Processed region 1: " + str(len(blocks))
end

Tick Budget

Each server tick (50ms game time), SScript processes completes:

  • Max 20 new processes can be spawned
  • Each running process executes ~50 statements
  • Remaining time runs normal Minecraft logic

Implication: Long-running tasks automatically spread across multiple ticks.


File Path Resolution

Files are resolved relative to server root:

// These are equivalent (both create sscripts/data/players.json):
file_write_json("sscripts/data/players.json", data)
file_write_json("sscripts/data/players.json", data)  // From server root

// These are NOT equivalent:
file_write_json("data.json", data)           // Creates in server root
file_write_json("logs/script.log", data)     // Creates in server root/logs

Best practice: Start paths from sscripts/ for organization.


Debugging Advanced Features

Enable Debug Output

/sscript debug on

This logs:

  • Every function call
  • Variable assignments
  • Selector evaluations
  • HTTP requests sent
  • File I/O operations

Tracing Async Execution

func slow_fetch(player):
    log "[FETCH] Starting for " + player
    wait http_get("https://api.example.com")
    log "[FETCH] Completed for " + player
end

on player_join(player):
    log "[EVENT] Player join: " + player.name
    slow_fetch(player)
    log "[EVENT] Handler complete"
end

// Output:
// [EVENT] Player join: Steve
// [EVENT] Handler complete
// [FETCH] Starting for Steve
// [FETCH] Completed for Steve

See Also


This site uses Just the Docs, a documentation theme for Jekyll.