-
Notifications
You must be signed in to change notification settings - Fork 0
Atomic Changes
An atomic change is done by the proxy, not by your server. The proxy handles one change at a time, so two servers can't lose each other's work.
A player has 100 coins and buys something on two servers at the same moment:
# WRONG
if {?coins::%uuid of player%} >= 100:
remove 100 from {?coins::%uuid of player%}
give player a diamondBoth servers read 100. Both think there's enough. Both subtract 100. The player gets two diamonds and pays for one.

Let the proxy do the check and the subtraction together:
# RIGHT
atomically remove 100 from {?coins::%uuid of player%} without going below 0 and wait
if the atomic change succeeded:
give player a diamond
else:
send "Not enough coins."
atomically add %number% to {?variable}
atomically remove %number% from {?variable}
atomically set {?variable} to %value% if it is not set
atomically set {?variable} to %value% if it is %value%
atomically remove %number% from {?variable} without going below %number%All of them take and wait at the end.
atomically add 50 to {?coins::%uuid of player%}
atomically remove 10 from {?coins::%uuid of player%}Numbers only. If the variable holds text, the proxy refuses:
{coins::123} holds a string, so it cannot be added to
Whole numbers stay exact, even above 9007199254740992 where normal decimal maths starts rounding.
A plain remove has no floor. It goes past zero without complaining:
atomically remove 500 from {?coins::%uuid of player%}
# balance is now -400subtract works as a word too.
atomically remove 250 from {?coins::%uuid of player%} without going below 0 and waitThe proxy works out the answer first. If it would land below the floor, it refuses and changes nothing:
{coins::123} holds 70, so taking that much would put it below the floor
Landing exactly on the floor is allowed, so removing 70 from 70 with a floor of 0 succeeds and leaves 0.
The floor doesn't have to be 0:
atomically remove 25 from {?fuel::%uuid of player%} without going below -100 and waitUse this form for anything a player spends.
atomically set {?coins::%uuid of player%} to 500 if it is not set and waitThis gives a starting balance once for the whole network, whichever server sees the player first. If the value already exists, the proxy refuses:
already set
Works with any type, not just numbers.
atomically set {?rank::%uuid of player%} to "vip" if it is "default" and waitThe proxy changes the value only if it's exactly what you said. Otherwise:
current value does not match
Two things trip people up here.
It never succeeds on a variable that doesn't exist yet. Use if it is not set for
the first time, then switch:
set {_last} to {?cooldown::%uuid of player%}
if {_last} is not set:
atomically set {?cooldown::%uuid of player%} to {_now} if it is not set and wait
else:
atomically set {?cooldown::%uuid of player%} to {_now} if it is {_last} and waitThe value you compare has to be the same type as the stored one. The proxy compares byte for byte. Wrapping a number in a string turns it into text, and text never matches a stored number:
atomically set {?count::1} to 5 if it is "%{_old}%" and wait # never matches
atomically set {?count::1} to 5 if it is {_old} and wait # correctQuotes are right when the value really is text, like a stored date.
This is how you claim something without two servers both claiming it:
atomically set {?arena::1} to network server name if it is not set and wait
if the atomic change succeeded:
send "This server got the arena."
else:
send "Already held by %{?arena::1}%"Without and wait, the change is sent and your script carries on. You never find out
what happened.
With and wait, your trigger pauses until the proxy answers.
atomically add 50 to {?coins::%uuid of player%} and wait
send "You now have %the atomic result% coins."the atomic result is the value the proxy holds after the change. For an add, that's
the new total. It's correct before the new value even reaches your server's copy.
Without and wait, this prints the old number:
atomically add 50 to {?coins::%uuid of player%}
send "You have %{?coins::%uuid of player%}%" # probably still the old value| Condition | Meaning |
|---|---|
the atomic change succeeded |
The proxy applied it. |
the atomic change was refused |
The proxy did not apply it. Nothing happened. |
the atomic change timed out |
No answer arrived. It may or may not have applied. |
the atomic change failed |
Refused or timed out. Use only if you treat both the same. |
the atomic change was answered |
An answer arrived, yes or no. |
Only a refusal proves nothing happened. On a timeout you don't know, so never give a reward and never tell the player they weren't charged.
atomically remove 100 from {?coins::%uuid of player%} without going below 0 and wait
if the atomic change succeeded:
give player a diamond
send "Bought. You have %the atomic result% left."
else if the atomic change timed out:
send "The network is slow right now. Try again in a moment."
else:
send "Not enough: %the atomic error%"the atomic error holds the refusal message, or the reason no answer came. It's empty
when the change succeeded.
The wait is 5 seconds by default. Change it with atomic-timeout in
the config.
the atomic change succeeded is a condition. You can't put it inside %...%.
# WRONG, this will not load
send "ok=%the atomic change succeeded%"Skript says Can't understand this expression. Use if and else:
# RIGHT
if the atomic change succeeded:
send "ok"
else:
send "not ok"the atomic result and the atomic error are expressions, so those work in a string.
and wait behaves like Skript's own wait effect. Everything after it runs on a later
tick, so the usual delay rules apply. You can't cancel the event after that line, and it
doesn't belong inside a function.
Local variables survive the wait, and a loop keeps its place:
set {_total} to 0
loop 3 times:
atomically add 10 to {?score::%uuid of player%} and wait
set {_total} to the atomic result
send "Total is %{_total}%" # 30Two triggers waiting at once each get their own answer. They can't read each other's.
Local variables, because they never leave the server:
atomically add 1 to {_count} # will not loadLists:
atomically add 1 to {?scores::*} # will not loadVariables with no prefix:
atomically add 1 to {coins} # will not loadSkript reports all three when the script loads.
These are the exact messages you get back in the atomic error.
| Message | Cause |
|---|---|
already set |
if it is not set on a value that exists |
current value does not match |
if it is X and the value was not X |
{name} holds a string, so it cannot be added to |
add or remove on a non numeric value |
{name} holds N, so taking that much would put it below the floor |
the floor blocked it |
the result does not fit in a whole number |
the number grew past the whole number limit |
can only add numbers, not 'X' |
the amount you gave was not a number |
{name} is a list, so it can only be deleted |
the name ended in ::*
|
no value attached |
the value was empty or could not be sent |
- Examples for complete scripts using all of this.
- Network variables for ordinary reading and writing.
- Limitations on why timeouts matter.
Guides
Reference