Skip to content

Examples

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

Complete scripts you can copy. Every one of these was run on a real three server network first.

Put them in plugins/Skript/scripts/ on each server, or push them from the proxy with script sharing so one copy runs everywhere.

A network economy

Balances follow the player everywhere. The starting balance is given once for the whole network, whichever server sees them first.

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

command /bal [<offline player>]:
	trigger:
		set {_target} to arg-1 ? player
		send "&e%{_target}%&7: &a$%{?eco::bal::%uuid of {_target}%} ? 0%"

command /pay <offline player> <number>:
	trigger:
		if arg-2 <= 0:
			send "&cAmount must be positive."
			stop
		if arg-1 is player:
			send "&cYou cannot pay yourself."
			stop

		atomically remove arg-2 from {?eco::bal::%uuid of player%} without going below 0 and wait
		if the atomic change was refused:
			send "&cNot enough money: %the atomic error%"
			stop
		if the atomic change timed out:
			send "&cThe network is slow. Check your balance before trying again."
			stop

		atomically add arg-2 to {?eco::bal::%uuid of arg-1%} and wait
		if the atomic change succeeded:
			send "&aSent $%arg-2% to %arg-1%. You have $%{?eco::bal::%uuid of player%}% left."
		else:
			# put it back, the other half did not go through
			atomically add arg-2 to {?eco::bal::%uuid of player%}
			send "&cCould not send that. Your money was returned."

/pay shows the careful shape. Take the money first with a floor, add it to the other player only once that succeeded, and put it back if the second half fails.

A shop that cannot be duped

command /price <text> <number>:
	permission: skript.admin
	trigger:
		set {?shop::price::%arg-1%} to arg-2
		send "&a%arg-1% now costs $%arg-2%"

command /buy <text>:
	trigger:
		set {_price} to {?shop::price::%arg-1%}
		if {_price} is not set:
			send "&cNo such item."
			stop

		atomically remove {_price} from {?eco::bal::%uuid of player%} without going below 0 and wait
		if the atomic change succeeded:
			give player 1 of arg-1 parsed as item type
			send "&aBought %arg-1% for $%{_price}%. Balance: $%the atomic result%"
		else if the atomic change timed out:
			send "&eThe network did not answer. Check your balance before buying again."
		else:
			send "&cCould not buy: %the atomic error%"

Two servers running this at once can't both succeed on the last coin. Tested with three servers, 20 purchases each, one balance of 30: 30 sold, 30 refused, balance 0.

Who is online, across the whole network

skNetwork keeps this list for you, so the short version is:

command /netlist:
	trigger:
		send "&e-- Network --"
		loop all network servers:
			send "&f%loop-value% &7(%size of network players on loop-value%)&7: %network players on loop-value%"

command /find <text>:
	trigger:
		set {_where} to network server of arg-1
		if {_where} is set:
			send "&a%arg-1% is on %{_where}%."
		else:
			send "&c%arg-1% is not online."

The proxy rebuilds that list from what each server reports, so a crash takes the server's players out of it by itself. See Network players.

The long version below keeps the list in a variable. It still works, and it shows the pattern for any per server list you keep yourself.

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

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

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

command /netlist:
	trigger:
		send "&e-- Network --"
		send "&flobby &7(%size of {?online::lobby::*}%)&7: %{?online::lobby::*}%"
		send "&flobby2 &7(%size of {?online::lobby2::*}%)&7: %{?online::lobby2::*}%"
		send "&fsurvival &7(%size of {?online::survival::*}%)&7: %{?online::survival::*}%"

command /find <text>:
	trigger:
		loop "lobby", "lobby2" and "survival":
			loop {?online::%loop-value-1%::*}:
				if loop-value-2 is arg-1:
					send "&a%arg-1% is on %loop-value-1%."
					stop
		send "&c%arg-1% is not online."

Note the nested loop. You can't write {?online::*::*}, because Skript only allows a * at the very end of a name. List the servers you have, or keep a second variable saying where each player is, like the stats example below.

The on network sync block is what makes this survive a crash. A server that dies never runs on quit, so its players would stay listed forever.

Cross-server statistics

One file, pushed to every server. It works on each one because it asks where it's running.

on join:
	set {?stats::server::%uuid of player%} to network server name
	set {?stats::seen::%uuid of player%} to now

on quit:
	set {?stats::seen::%uuid of player%} to now

on death of player:
	atomically add 1 to {?stats::deaths::%uuid of victim%}
	if attacker is a player:
		atomically add 1 to {?stats::kills::%uuid of attacker%}
		atomically add 50 to {?eco::bal::%uuid of attacker%}

command /stats [<offline player>]:
	trigger:
		set {_t} to arg-1 ? player
		set {_u} to uuid of {_t}
		send "&e%{_t}%"
		send "&7kills &f%{?stats::kills::%{_u}%} ? 0%&7  deaths &f%{?stats::deaths::%{_u}%} ? 0%"
		send "&7last on &f%{?stats::server::%{_u}%} ? "unknown"%&7 at &f%{?stats::seen::%{_u}%} ? "never"%"

Use atomically add for the counters. Two servers can kill the same player at nearly the same moment, and a plain add 1 would lose one of them.

A daily reward, once per network per day

command /daily:
	trigger:
		set {_today} to now formatted as "yyyy-MM-dd"

		atomically set {?daily::%uuid of player%} to {_today} if it is not set and wait
		if the atomic change succeeded:
			atomically add 100 to {?eco::bal::%uuid of player%} and wait
			send "&aFirst daily reward. +$100, balance $%the atomic result%"
			stop

		set {_last} to {?daily::%uuid of player%}
		if {_last} is {_today}:
			send "&7You already claimed today. Come back tomorrow."
			stop

		atomically set {?daily::%uuid of player%} to {_today} if it is "%{_last}%" and wait
		if the atomic change succeeded:
			atomically add 100 to {?eco::bal::%uuid of player%} and wait
			send "&aDaily reward. +$100, balance $%the atomic result%"
		else:
			send "&eSomeone claimed that on another server a moment ago."

The second form matters. Two servers could both see yesterday's date and both pay out. By saying "change it to today only if it's still yesterday", exactly one wins.

Claiming something only one server may hold

An event, an arena, a boss spawn. Only one server should run it.

command /claim <text>:
	trigger:
		atomically set {?lock::%arg-1%} to network server name if it is not set and wait
		if the atomic change succeeded:
			send "&a%network server name% now holds %arg-1%"
		else:
			send "&e%arg-1% is already held by %{?lock::%arg-1%}%"

command /release <text>:
	trigger:
		atomically set {?lock::%arg-1%} to "free" if it is network server name and wait
		if the atomic change succeeded:
			delete {?lock::%arg-1%}
			send "&aReleased %arg-1%"
		else:
			send "&cThat is not ours to release: %the atomic error%"

release uses the compare form so one server can't release a lock another server holds.

A network wide cooldown

command /kit:
	trigger:
		set {_now} to unix timestamp of now
		set {_last} to {?cooldown::kit::%uuid of player%}

		# first time ever: the variable does not exist, so use the "not set" form
		if {_last} is not set:
			atomically set {?cooldown::kit::%uuid of player%} to {_now} if it is not set and wait
			if the atomic change succeeded:
				give player 1 diamond sword
				send "&aHere is your kit."
			else:
				send "&cYou just claimed that on another server."
			stop

		if {_now} - {_last} < 3600:
			set {_left} to 3600 - ({_now} - {_last})
			send "&cWait %{_left}% more seconds. Switching servers will not help."
			stop

		atomically set {?cooldown::kit::%uuid of player%} to {_now} if it is {_last} and wait
		if the atomic change succeeded:
			give player 1 diamond sword
			send "&aHere is your kit."
		else:
			send "&cYou just claimed that on another server."

Two details here are easy to get wrong, and both turned up in testing.

The first claim needs the if it is not set form. The if it is ... form can never succeed against a variable that doesn't exist yet, so a player's very first /kit would always be refused.

The comparison uses {_last}, not "%{_last}%". Wrapping it in a string turns a number into text, and the proxy compares exactly, so text never matches a stored number.

A hub command

Every server pushes the same file, and /hub moves the player to the lobby unless they're already there or it's down.

command /hub:
	trigger:
		if network server "lobby" is not online:
			send "&cThe lobby is down right now."
			stop
		if network server name is "lobby":
			send "&eYou are already in the lobby."
			stop
		send "&7Sending you to the lobby..."
		connect network player "%player%" to "lobby"

The two checks come first because connect does nothing when the target is missing, and a player would otherwise wait for a move that never comes.

Paying someone on another server

The /pay from the economy example, with the receiver told wherever they are.

command /pay <offline player> <number>:
	trigger:
		if arg-2 <= 0:
			send "&cAmount must be positive."
			stop
		set {_where} to network server of "%arg-1%"
		if {_where} is not set:
			send "&c%arg-1% is not online anywhere."
			stop

		atomically remove arg-2 from {?eco::bal::%uuid of player%} without going below 0 and wait
		if the atomic change was refused:
			send "&cNot enough money: %the atomic error%"
			stop
		if the atomic change timed out:
			send "&cThe network is slow. Check your balance before trying again."
			stop

		atomically add arg-2 to {?eco::bal::%uuid of arg-1%} and wait
		if the atomic change succeeded:
			send "&aSent $%arg-2% to %arg-1% on %{_where}%."
			send network message "&a%player% sent you $%arg-2%. You now have $%the atomic result%." to network player "%arg-1%"
		else:
			atomically add arg-2 to {?eco::bal::%uuid of player%}
			send "&cCould not send that. Your money was returned."

The receiver gets the message on whichever server they're standing on. If they logged off in the meantime, the money still arrived and only the message is lost.

A live scoreboard, on every server

One server changes a score, every server sees it change, without polling.

on network variable change of "score::*":
	set {_team} to the last element of the changed variable split at "::"
	broadcast "&6%{_team}% &7now has &f%the new value% &7points (was %the old value% ? 0)"

command /score <text> <number>:
	permission: skript.admin
	trigger:
		atomically add arg-2 to {?score::%arg-1%}

the old value is right even on the server that ran /score, because the proxy supplies it. Reading the variable yourself inside the event would already show the new number there.

Telling players when the network is down

on network disconnect:
	broadcast "&cLost the network. Money and stats are read only for now."

on network sync:
	broadcast "&aNetwork is back."

command /netstatus:
	trigger:
		if network is synced:
			send "&aNetwork is up. This server is %network server name%."
		else:
			send "&cNetwork is down. Values you see may be out of date."

A safe join handler

Everything that touches the network on join, in the order that works.

on join:
	if network is not synced:
		send "&eThe network is still starting up. Some things may not work yet."
		stop

	atomically set {?eco::bal::%uuid of player%} to 500 if it is not set
	atomically add 1 to {?eco::joins::%uuid of player%}
	set {?stats::server::%uuid of player%} to network server name
	add player's name to {?online::%network server name%::*}

	wait 1 tick
	send "&aWelcome back. Balance: $%{?eco::bal::%uuid of player%} ? 500%"

The guard at the top isn't optional. Before the first sync every is not set check says yes, so without it this hands a fresh 500 to a player who already has money.

Things to avoid

# reading then writing, on a network
if {?coins::%uuid of player%} >= 100:
	remove 100 from {?coins::%uuid of player%}

Both servers read the old value and both subtract. Use atomically remove ... without going below 0.

# writing every tick for every player
every tick:
	loop all players:
		set {?pos::%uuid of loop-player%} to location of loop-player

Every one of those is a message to the proxy. This slows the whole network down. Write on a real event instead, or every few seconds at most.

# reading a value you just changed
atomically add 50 to {?coins::%uuid of player%}
send "You have %{?coins::%uuid of player%}%"

The second line usually prints the old number. Add and wait and use the atomic result.

# deleting a huge list while players are online
delete {?bigdata::*}

The proxy checks every variable it holds to do this, and nothing else is written while it works. Do it at a quiet time.

See also

Clone this wiki locally