Accumulators
An accumulator is a special type of field that can store a sortable number on a player entity
persistently between games. It is used for generating ranked leaderboards.
pub accumulator owner.NumLifetimeGames
pub game fn World.Main() {
SpawnEachPlayer owner {
NumLifetimeGames(delta=1, showOnLeaderboard=true)
}
}
pub page fn owner.HomePage() {
Content {
P { "Loading leaderboard..." }
}
let leaderboard = await FetchLeaderboard([&NumLifetimeGames])
Content {
for player in leaderboard %%{
P {
%(player.PlayerName + " has played " + player.NumLifetimeGames + " games")
}
}
}
}
Declaration
Every accumulator must have an object parameter, and the object parameter must be owner,
representing the player who owns the accumulator.
The accumulator's name must begin with an uppercase letter.
accumulator owner.NumLifetimeGames
An accumulator may optionally be given an initial value.
accumulator owner.Rating = 1000
Accumulators can be declared as public using the pub keyword,
which allows them to be accessed from all files in the program.
Otherwise they are private and can only be accessed from within the same file.
It is not possible to have two accumulators with the same name, even if they are both private and in separate files.
Doing so will generate a compiler error.
This is because the accumulator name forms a globally unique key
which is used to look up the value from persistent storage.
pub accumulator owner.NumLifetimeGames = 0
Reading an accumulator
An accumulator can be read the same way as any other property:
let value = owner.Rating
The object parameter owner must be either a player entity,
or one of the entries returned by FetchLeaderboard.
If you can omit the owner parameter, it will be found from context:
let value = Rating // `owner` found from context
It is possible to await until accumulator changes.
Like all other await functions, it is possible to use with, on or once to handle the change.
See behaviors for more details on how to use these.
with Rating {
Transmission {
P { "Your current rating: " + Rating }
}
}
Modifying an accumulator
Accumulators can be modified using an assignment statement.
Let's say we have defined an accumulator called ExampleAccumulator1:
pub accumulator owner.ExampleAccumulator1 = 0
An assignment statement can be used with ExampleAccumulator1 to increase, decrease or overwrite its value.
There are also a number of other parameters which will be described in more detail below.
For example:
owner.ExampleAccumulator1 = 123 // set to 123
owner.ExampleAccumulator1(showOnLeaderboard=true) += 5 // increase by 5 and show on leaderboard permanently
owner.ExampleAccumulator1(resetAfter=5d) -= 10 // decrease by 10 then delete after 5 days
Parameters
The signature for calling an accumulator is as follows:
owner.ExampleAccumulator1(revertAfter?, resetAfter?, resetInterval?, showOnLeaderboard?) = value
owner.ExampleAccumulator1(revertAfter?, resetAfter?, resetInterval?, showOnLeaderboard?) += value
owner.ExampleAccumulator1(revertAfter?, resetAfter?, resetInterval?, showOnLeaderboard?) -= value
-
owner(Entity): must refer to the player entity that owns the accumulator. If it is nullish, will do nothing. -
value(Number): the value to set, increase or decrease the accumulator by, depending on the operator used. Directly setting an accumulator's value (using=) cancels all scheduled changes to the accumulator, for example from therevertAfter,resetAfterorresetIntervalparameters. -
revertAfter(Number): if specified, will revert the delta after this duration. This would normally be specified in minutes, hours or days. For example30m,4h,30d. Reversions are processed once every minute by the server, and so any duration smaller than that will not happen exactly on time. The maximum allowable value is365d, any values larger than this will be clamped. AsrevertAfteris processed by the server, if it occurs while a player is in a game, the player will only see the new value upon joining a new game. -
resetAfter(Number): Schedules the accumulator to be reset to its initial value after this duration. A nullish value means the accumulator will never be reset to its initial value. This replaces any previously-defined scheduled reset. This parameter would normally be specified in minutes, hours or days. For example30m,4h,30d. The maximum allowable value is365d, any values larger than this will be clamped. Resets are processed once every minute by the server, and so any duration smaller than that will not happen exactly on time. AsresetAfteris processed by the server, if a reset occurs while a player is in a game, they will only see the new value upon joining a new game. -
resetInterval(Number): Schedules the accumulator to be reset to its initial value after this duration. If there is already a reset scheduled, this does nothing unless the new interval is shorter. If this parameter is nullish, deletes any previously-defined scheduled reset. This can be used to create a daily or weekly leaderboard, for example. This parameter would normally be specified in minutes, hours or days. For example30m,4h,30d. The maximum allowable value is365d, any values larger than this will be clamped. AsresetIntervalis processed by the server, if a reset occurs while a player is in a game, they will only see the new value upon joining a new game. -
showOnLeaderboard(Boolean or Number): if specified, will include the player in any calls to FetchLeaderboard for this duration. Iftrue, will show on the leaderboard permanently. If a Number, will show on the leaderboard for that duration.
If you are wanting to adjust an accumulator's value by a specific amount,
you should assign it with += or -= instead, because that guarantees that no data is lost
if multiple changes to the accumulator are made at the same time.
Resetting an accumulator
To reset an accumulator to its initial state,
use the delete keyword:
delete owner.ExampleAccumulator1
This not only sets the accumulator to its initial value, it also removes any scheduled resets, reversions and removes it from the leaderboard.
Leaderboards
When modifying an accumulator,
there is a showOnLeaderboard parameter, which determines whether
the player will be included when in the leaderboard for that particular accumulator.
If the showOnLeaderboard parameter is set to true or false, the player will be included or excluded
from the leaderboard permanently until the value is changed.
However, it is also possible to limit the player to be shown on the leaderboard for a particular duration
by passing a duration value to the showOnLeaderboard parameter.
Players are expired from the leaderboard every minute by the server,
and so durations much smaller than one minute will not happen exactly on time.
// add 5 rating, and show on leaderboard forever
Rating(showOnLeaderboard=true) += 5
// add 5 rating, and show on leaderboard for 30 days
Rating(showOnLeaderboard=30d) += 5
The FetchLeaderboard function is used to retrieve a list of players sorted by their accumulator values. It takes a list of accumulators to order by. The player must be included in the leaderboard for the first accumulator to be included in the list. The remaining accumulators are only used as tiebreakers. The leaderboard is always sorted from highest to lowest.
FetchLeaderboard returns an Array of Maps,
where each Map contains a PlayerName and UserId field,
as well as a field for each accumulator.
let leaderboard = await FetchLeaderboard([&Rating])
for player in leaderboard %%{
P {
%(player.PlayerName + " has " + player.Rating + " rating and " + player.NumLifetimeGames + " games")
}
}
Scoreboards
There are a couple of built-in functions for displaying live scoreboards in-game:
- Scoreboard lists the players in this particular instance of the game
- OnlinePlayerScoreboard lists all online players
pub game fn World.Main() {
StatusContent {
H3 { "Most Games" }
OnlinePlayerScoreboard(&NumLifetimeGames)
}
}
Profiles
The FetchUserAccumulators can be used to fetch the accumulators for one particular player. It can be used to create profile pages for your players, for example to show their ranking, number of games played, or any other statistics you are tracking with accumulators.
pub page fn owner.ProfilePage(userId) {
let profile = await FetchUserAccumulators(userId)
if profile {
Content {
P { "Games played: " + profile.NumLifetimeGames }
P { "Rating: " + profile.Rating }
}
} else {
Content {
"Sorry, this player does not exist!"
}
}
}
Synchronization between clients and server
Accumulators are designed to be used for leaderboards. They are designed to store slow-moving data that does not need to be perfectly up-to-date in real time.
When a player joins a game, their accumulators are loaded from the database and sent to all clients. From that point onward, the client performs dead reckoning to keep track of the value locally. Players should only be playing one game at a time, and so this should work perfectly almost all the time, as all changes to accumulators should be made by the game that the player is currently in.
If the server changes an accumulator value, for example due to a scheduled reset or reversion, the new value will not be detected until the player joins the next game. Scheduled resets are designed to be used for long timescales like hours, days or weeks, so this should not be noticeable for a normal game.
Accumulator changes are accumulated in memory and then committed in batches to the database every 5-10 seconds, which means there will be short delays before changes are reflected on the leaderboard.