Skip to content

Guide · Step 08

Player & World Properties

You'll build a /wall command that teleports with a one-line write — plus three real compile errors that never reach your server.

Dotted chains — event.player.location.y, args.target.held_item.amount — are how scripts read and write game state. They look like field access; under the hood every hop goes through a property registry: a closed, typed table of what each kind of value exposes, which hops are writable, how values are coerced, and which thread the real server call must run on.

The compiler ships a copy of that table. Every chain in your script is checked at compile time — unknown properties, read-only writes, and type mismatches never reach the server.

Reading

swoftlang
event PlayerChat {
    execute {
        send "you are ${event.player.name} at y=${event.player.location.y}" to event.player
        send "holding ${event.player.held_item.material}" to event.player
    }
}

Chains resolve left to right: event → the event object, .player → a Player, .location → a Location snapshot, .y → a Double. Interpolated ${...} paths are the same chains with the same checks.

Misspell a hop and the compiler answers with a suggestion:

swoftlang
event PlayerChat {
    execute {
        send "ping: ${event.player.latencey}ms" to event.player
    }
}
txt
ping.sw:3:14: error: unknown property 'latencey' on Player; did you mean 'latency'?
        send "ping: ${event.player.latencey}ms" to event.player
             ^

Writing

set <chain> to <value> writes through the same table:

swoftlang
event PlayerChat {
    execute {
        set event.player.health to event.player.max_health
        set event.player.gamemode to "creative"
        set event.player.held_item.amount to 32
    }
}

Values are coerced and validated per property: numbers clamp to sane ranges (health to 0..max_health, item amounts to 1..99), and enum-like strings are checked at compile time:

swoftlang
command "gm" {
    execute {
        if sender is a Player {
            set sender.gamemode to "hardcore"
        }
    }
}
txt
gm.sw:4:36: error: invalid gamemode 'hardcore'; valid values: survival, creative, adventure, spectator
            set sender.gamemode to "hardcore"
                                   ^

Read-only properties refuse writes the same way:

swoftlang
event PlayerChat {
    execute {
        set event.player.name to "Somebody"
    }
}
txt
rename.sw:3:26: error: property 'name' on Player is read-only
        set event.player.name to "Somebody"
                         ^

In Skript each property has its own expression syntax, and a wrong guess fails at runtime; here it's one set ... to shape over a checked table:

reading and writing player state
skript
command /vitals:
    trigger:
        send "you are at y=%y-coordinate of player%"
        set player's gamemode to creative
        heal player
        set player's level to 10
swoftlang
command "vitals" {
    execute {
        if sender is a Player {
            send "you are at y=${sender.location.y}"
            set sender.gamemode to "creative"
            set sender.health to sender.max_health
            set sender.level to 10
        }
    }
}
Why it maps this way

Skript has a bespoke phrase per property (y-coordinate of, heal, possessives); SwoftLang has one chain syntax, and the compiler knows every valid hop — so typos get did-you-mean suggestions instead of "can't understand this expression" at reload time.

How set player.location.x actually works

Some values in the chain are immutable — a Location is a snapshot record, an Item is an immutable stack. You can still write through them:

swoftlang
command "wall" {
    execute {
        if sender is a Player {
            set sender.location.x to 100.5
        }
    }
}

The runtime resolves the chain and finds the deepest hop that has a real setter — the anchor. Here that's location, whose setter is teleport. Hops past the anchor are copy hops: read the current position, copy it with x replaced, teleport once — y, z, yaw, and pitch preserved, all within a single tick. The same mechanism drives set sender.held_item.amount to 32: read held item, copy with the new amount, set in hand.

Snapshots don't move players

set sender.location.x to ... teleports, because the anchor is the player. But a location stored in a variable is just a snapshot — writing to it updates the variable and nothing else:

swoftlang
event PlayerChat {
    execute {
        set spot to event.player.location
        set spot.y to 300.0
        send "spot.y = ${spot.y}, and you have not moved" to event.player
    }
}

Here the anchor is the local variable spot, so the copy lands back in the variable. To actually move someone using a snapshot: set event.player.location to spot (or teleport event.player to spot).

World properties

world(name) looks a world up (it's an optional<World>Step 07 applies), and worlds expose time control:

swoftlang
command "day" {
    execute {
        set w to world("lobby")
        if w exists {
            set w.time to 1000
            set w.time_rate to 1
            send "<yellow>Sunrise." to sender
        }
    }
}

Creating and loading worlds is a deployment concern — that's Step 16.

Threading

Every property row carries a thread policy. Reads and packet-only writes are safe from any thread; world-mutating writes (teleport, health, gamemode, inventory) are tick-only and are automatically dispatched to the tick thread when a script runs async. You never write dispatch code — the table does it.

The property tables

What the registry — and therefore the compiler — recognizes:

Player

PropertyTypeAccess
nameStringread-only
uuidStringread-only
healthDoubleread/write (clamped 0..max_health)
max_healthDoubleread/write
foodIntegerread/write (clamped 0..20)
food_saturationDoubleread/write
levelIntegerread/write
expDoubleread/write (0.0..1.0)
gamemodeStringread/write (survival/creative/adventure/spectator)
locationLocationread/write (write = teleport)
worldWorldread/write (write = instance transfer)
held_itemItemread/write
held_slotIntegerread/write (0..8)
latencyIntegerread-only
display_nameStringread/write
flyingBooleanread/write
allow_flyingBooleanread/write
flying_speedDoubleread/write
onlineBooleanread-only
skinSkinread/write (from skin(texture, signature) or async fetch_skin(name))

Location

PropertyTypeAccess
x, y, zDoubleread/write (copy hop)
yaw, pitchDoubleread/write (copy hop; pitch clamped -90..90)
block_x, block_y, block_zIntegerread-only (derived)

Item

PropertyTypeAccess
materialStringread/write (accepts stone or minecraft:stone)
amountIntegerread/write (1..99)
nameStringread/write
lorelist<String>read/write
stats, tagscustom-item namespaces — Step 12

World

PropertyTypeAccess
timeIntegerread/write
time_rateIntegerread/write

Event objects

Listed per event in the event table. Later steps add Mob (Step 12) and Display (Step 13) values with tables of their own.

A closed whitelist is the feature

There is deliberately no reflection fallback exposing the whole server API to scripts. If a property isn't in the table, it doesn't exist — which is what makes "did you mean latency?" possible, keeps scripts working across server versions, and gives every write a validated, thread-correct path.