Skip to content

Sync and Events

Ahmad Saleem edited this page Sep 5, 2026 · 6 revisions

A server doesn't have the network data the moment it starts. It has to connect to the proxy and receive everything first. That gap is short, but scripts that ignore it break in ways that are hard to find later.

This page covers the startup rule, the three events, and network server name.

The startup problem

When a server starts, its copy of the network variables is empty.

Empty isn't the same as "the network has nothing". During that gap this says yes even for players who already have data:

if {?coins::%player%} is not set:
	set {?coins::%player%} to 500   # about to wipe a real balance

So skNetwork refuses every write until the first sync finishes. Refused writes are counted and logged:

refused a write to {?coins::abc}: not synced with the proxy (SYNCING).
3 write(s) dropped so far. Guard writes with 'network is synced'

Reads still work. They just answer from an empty copy.

Sequence diagram: lobby sends HELLO, the proxy replies WELCOME, SNAPSHOT chunks, then SYNCED. Only after SYNCED are writes accepted and on network sync fires.

network is synced

if network is synced:
	# safe to read and write
if network is not synced:
	# still starting up

Guard anything that runs early:

on join:
	if network is not synced:
		kick player due to "The network is still starting. Try again in a moment."
		stop
	atomically set {?coins::%player%} to 500 if it is not set

It also tells you whether the proxy is reachable right now:

command /netstatus:
	trigger:
		if network is synced:
			send "Network is up."
		else:
			send "Network is down. Balances are read only."

on network sync

Fires once the copy is filled in and writes are accepted.

on network sync:
	send "Network ready on %network server name%" to console

It fires again after every reconnect, not only at startup. That makes it the right place to rebuild anything this server owns.

The usual case is a cross-server player list. A server that crashes never runs on quit, so its players would stay in the list forever. Clearing and rebuilding on every sync fixes that:

on network sync:
	delete {?online::%network server name%::*}
	loop all players:
		add loop-player's name to {?online::%network server name%::*}

A crash now only leaves stale names until that server comes back.

on network disconnect

Fires when the server loses the proxy.

on network disconnect:
	broadcast "&cLost the network. Money is read only for now."

Reads keep working from the copy the server already holds. Writes are refused until it reconnects.

Reconnecting is automatic. The wait starts at 1 second and doubles up to 30 seconds.

Sequence diagram: after losing the proxy, lobby sends HELLO with the last sequence number it saw, the proxy answers WELCOME resuming, replays only the missed DELTAs, then SYNCED, and on network sync fires again.

A proxy that goes quiet without closing the connection is noticed after six unanswered pings, half a minute. The server drops the connection itself and the next sync pulls everything, in case writes sent into the silence never arrived.

Both events were checked by running /sknet reconnect on a live server:

EVENT: network disconnect fired on lobby
EVENT: network sync fired on lobby

They arrived one second apart.

on network variable change

Fires on every server the moment a network variable changes, including the server that wrote it.

on network variable change:
	broadcast "%the changed variable% is now %the new value%"

Give it a name to listen to one branch instead of every write on the network. A * is a wildcard, the same as /sknetproxy dump:

on network variable change of "coins::*":
	send "%the changed variable% went from %the old value% to %the new value%" to console

Three expressions work inside it:

Expression Holds
the changed variable The name, without the prefix, the way it travels between servers.
the new value The value now. Unset when the variable was deleted.
the old value The value before the change. Unset when there was none, and for a list delete.

The old value comes from the proxy, not from this server's copy. That matters on the server that made the change, because Skript updates its own copy before the change comes back, so a local read would already show the new number.

Names are lowercased, both the one you write after of and the one in the changed variable, because Skript treats variable names that way.

Sequence diagram: lobby sets coins::notch to 150 and sends MUTATE, the proxy records 100 becoming 150 and sends a DELTA carrying both values to lobby and survival, and the event fires on both with new value 150 and old value 100.

Nothing fires while a snapshot is arriving, so a server joining doesn't replay the whole map as changes. A reconnect that only catches up on what it missed is quiet in the same way.

A common use is a cross-server inbox: write to a variable named after the player, and whichever server has them delivers it.

command /msg <text> <text>:
	trigger:
		set {?inbox::%arg-1%} to "%player%: %arg-2%"

on network variable change of "inbox::*":
	set {_name} to the last element of the changed variable split at "::"
	set {_who} to {_name} parsed as player
	if {_who} is online:
		send "&d[mail] %the new value%" to {_who}

Deleting a list fires once, with the list name, not once per entry:

delete {?team::red::*}
# fires with the changed variable = "team::red::*" and no new value

network server name

The name of the server the script is running on. It comes from server-name in that server's plugins/skNetwork/config.yml.

send "This server is %network server name%"

name of this network server means the same thing.

This is what lets one script go to every server and still behave differently on each:

on join:
	add player's name to {?online::%network server name%::*}
	set {?stats::server::%player%} to network server name

on quit:
	remove player's name from {?online::%network server name%::*}

Every server runs the same file and writes to its own list. Read the lists back for a network wide view:

command /netonline:
	trigger:
		send "lobby:    %size of {?online::lobby::*}%"
		send "lobby2:   %size of {?online::lobby2::*}%"
		send "survival: %size of {?online::survival::*}%"

If server-name is blank, the name becomes server- plus the port, like server-25567. Set it properly. Two servers sharing a name causes real problems, see Limitations.

Putting it together

A starting balance given once for the whole network, on whichever server sees the player first:

on join:
	if network is not synced:
		stop
	atomically set {?eco::bal::%player%} to 500 if it is not set
	atomically add 1 to {?eco::joins::%player%}
	set {?stats::server::%player%} to network server name

The if it is not set form means it doesn't matter that this runs on three servers. Only the first one wins.

See also

Clone this wiki locally