-
Notifications
You must be signed in to change notification settings - Fork 0
Stats
Since v0.3.0. steam.stats is the curated layer over ISteamUserStats: the
achievements and the per-user stats of the logged-in account.
Steam loads the current user's stats during init, so the read methods answer
right away. Almost everything here is a local read against the client's copy.
unlock, clear, store and resetAll are the only synchronous methods that
send anything to Steam, and five methods are async:
getNumberOfCurrentPlayers,
getGlobalPercentages,
requestUserStats,
requestGlobalStats and
achievementIcon.
Failures come out in two shapes. A flat call that returns false, which for
this interface almost always means the API name is not one this app defines,
throws a plain Error prefixed with steamwand:. An async call that Steam
refuses throws SteamResultError with the EResult attached. See
Errors.
The Stats instance is created lazily and cached on the Steam object. The
leaderboard half of the same interface lives on Leaderboards.
isAchieved(name: string): booleanWhether the current user unlocked the achievement. name is the API name from
the partner site, not the display name.
if (!steam.stats.isAchieved('ACH_WIN_ONE_GAME')) {
steam.stats.unlock('ACH_WIN_ONE_GAME');
}Throws Error: steamwand: GetAchievement returned false (...) when the app has
no achievement with that API name.
getAchievement(name: string): AchievementStateThe same read plus the unlock time. unlockTime is Unix seconds while the
achievement is unlocked and null while it is locked, so there is no epoch-zero
date to filter out.
const { achieved, unlockTime } = steam.stats.getAchievement('ACH_WIN_ONE_GAME');
if (achieved) console.log(new Date(unlockTime! * 1000));Throws the same Error as isAchieved for an unknown API name.
getDisplay(name: string): AchievementDisplayThe display name, the description and the hidden flag, in the Steam client
language. The text comes from the achievement configuration on the partner site,
so a field that was never configured reads back as an empty string, and an
unknown API name gives three empty fields rather than an error. A hidden
achievement has an empty description until the user unlocks it.
Since v0.6.0.
achievementIcon(name: string): Promise<number | null>The Steam image handle of the achievement's icon, or null when the
achievement has none configured. Decode the pixels with
system.image.
Steam either has the icon cached, in which case the promise resolves at once, or
it starts a download and answers with UserAchievementIconFetched_t. Both paths
end here, so there is no handle of 0 to check for. The icon is the locked or
the unlocked one, whichever matches the current state, so read it again after
unlock.
const handle = await steam.stats.achievementIcon('ACH_WIN_ONE_GAME');
const icon = handle === null ? null : steam.system.image(handle);
console.log(icon?.width, icon?.height);An API name this app does not define never gets an answer. Steam does not say "no such achievement" here, so the promise waits until the session closes. Check the name against listAchievements first.
listAchievements(): string[]The API names of every achievement this app defines, in partner-site order.
Steam knows the list only once it has the app's stats schema, so an empty array
right after init means the schema has not arrived yet, not that the app has no
achievements.
for (const name of steam.stats.listAchievements()) {
console.log(name, steam.stats.getDisplay(name).name, steam.stats.isAchieved(name));
}unlock(name: string): voidSets the achievement and stores immediately, because Steam draws the unlock toast on the store. Unlocking an already unlocked achievement does nothing. Unlike the stat setters, this one needs no separate store.
Throws for an unknown API name, and for a failed store (naming StoreStats).
clear(name: string): voidLocks the achievement again and stores. Meant for testing; a released game normally never clears an achievement.
indicateProgress(name: string, current: number, max: number): voidDraws the progress toast ("30 of 100 kills") and nothing else. It does not
unlock the achievement and it does not record the progress anywhere: keep the
count in a stat and call unlock yourself when it reaches max. Steam
ignores the call when current is not above the last value it showed.
const kills = steam.stats.getInt('kills') + 1;
steam.stats.setInt('kills', kills);
steam.stats.store();
if (kills < 100) steam.stats.indicateProgress('ACH_100_KILLS', kills, 100);
else steam.stats.unlock('ACH_100_KILLS');Since v0.6.0.
getProgressLimits(name: string): { min: number; max: number } | nullThe progress range the partner site configured for the achievement, or null
for an achievement with no progress configured. Saves hard-coding the "of 100"
in indicateProgress, so the toast stays right when the
configuration changes.
const limits = steam.stats.getProgressLimits('ACH_100_KILLS');
if (limits) {
steam.stats.indicateProgress('ACH_100_KILLS', steam.stats.getInt('kills'), limits.max);
}getInt(name: string): numberAn INT stat of the current user. A stat the user never set reads back as 0. Throws for an API name the app does not define as an INT stat.
getFloat(name: string): numberA FLOAT stat, or an AVGRATE stat, which reads back as the current average.
setInt(name: string, value: number): voidThe value stays local until store runs. That is deliberate: set
every stat that changed, then store once. Steam rejects a value above the
maximum the partner site configured, which surfaces at store, not here.
setFloat(name: string, value: number): voidSame, for a FLOAT stat. Local until store.
updateAvgRate(name: string, sessionCount: number, sessionLength: number): voidFeeds one session into an AVGRATE stat, for example kills per hour. Steam keeps
the running average itself; read it back with getFloat.
sessionLength is in the unit the stat is configured with, usually seconds.
Local until store.
steam.stats.updateAvgRate('kills_per_hour', 42, 1800);
steam.stats.store();store(): voidSends every pending stat change to Steam. Nothing the setters wrote is persisted until this runs, so call it at a natural break: end of a level, end of a match, shutdown. It returns as soon as Steam accepted the batch, and the server round trip finishes in the background.
Throws Error: steamwand: StoreStats returned false (...) when Steam refuses
the batch, which means it is not logged on, or a value is above its configured
maximum.
resetAll(alsoAchievements?: boolean): voidResets every stat of the current user, and every achievement too when
alsoAchievements is true (default false). It stores by itself and it
cannot be undone. Meant for testing.
getNumberOfCurrentPlayers(): Promise<number>How many people are playing this app right now.
console.log(await steam.stats.getNumberOfCurrentPlayers(), 'players online');Throws Error: steamwand: GetNumberOfCurrentPlayers failed (is Steam online?)
when Steam sets the failure flag on the result, which is what an offline client
does.
getGlobalPercentages(): Promise<Record<string, number>>For every achievement of this app, the share of players who unlocked it, 0 to
100. One round trip (RequestGlobalAchievementPercentages) fills Steam's local
cache, then every percentage is read out of it. An achievement Steam has no data
for is absent from the record, so read it with ?? 0.
const percent = await steam.stats.getGlobalPercentages();
console.log(percent['ACH_WIN_ONE_GAME']?.toFixed(1), '% of players');Throws SteamResultError with operation: 'RequestGlobalAchievementPercentages'
on a non-OK result, for example k_EResultFail while the client is offline.
requestUserStats(steamId: bigint): Promise<void>Downloads another user's stats and achievements into Steam's local cache. The current user's are already there, so this is only for other people.
The method returns nothing on purpose. Once it resolves, the values are in the cache and you read them with getUserAchievement, getUserInt and getUserFloat.
const friend = 76561197960287930n;
await steam.stats.requestUserStats(friend);
console.log(steam.stats.getUserInt(friend, 'kills'));Throws SteamResultError with operation: 'RequestUserStats', typically
k_EResultFail when the other user's profile is private.
Since v0.6.0.
getUserAchievement(steamId: bigint, name: string): AchievementStateAnother user's achievement with its unlock time, in the same AchievementState shape as getAchievement.
requestUserStats must have resolved for that user
first. This is a local read against the cache it filled, so a user who is not
in the cache throws the same plain Error as an unknown API name.
await steam.stats.requestUserStats(friend);
console.log(steam.stats.getUserAchievement(friend, 'ACH_WIN_ONE_GAME').achieved);Since v0.6.0.
getUserInt(steamId: bigint, name: string): numberAnother user's INT stat, 0 when they never set it. requestUserStats must have resolved for that user first.
Since v0.6.0.
getUserFloat(steamId: bigint, name: string): numberAnother user's FLOAT or AVGRATE stat, the latter as their current average. requestUserStats must have resolved for that user first.
Since v0.6.0.
requestGlobalStats(historyDays?: number): Promise<void>Downloads this app's aggregated global stats into Steam's local cache. Global stats are the sums over every player, and only a stat the partner site marks as aggregated has one. Nothing in this group reads back before this resolves, so it is always the first call.
historyDays is how many days of daily history to fetch as well, at most 60,
default 0 for the totals only. getGlobalIntHistory
reads no more days than were requested here.
await steam.stats.requestGlobalStats(7);
console.log(steam.stats.getGlobalInt('kills_total'));
console.log(steam.stats.getGlobalIntHistory('kills_total', 7));Throws SteamResultError with operation: 'RequestGlobalStats', for example
k_EResultInvalidState when the app aggregates no stat at all.
Since v0.6.0.
getGlobalInt(name: string): bigintThe global total of an INT stat. It sums over every player, so it is 64-bit and
comes back as a bigint. requestGlobalStats must have
resolved first.
Since v0.6.0.
getGlobalDouble(name: string): numberThe global total of a FLOAT stat. requestGlobalStats must have resolved first.
Since v0.6.0.
getGlobalIntHistory(name: string, days: number): bigint[]The daily history of an INT global stat, one total per day, today first.
requestGlobalStats must have resolved with at least this
many historyDays, or the history is not in the cache.
The array is shorter than days when Steam has less history, and empty when it
has none, so read it by length rather than by index.
await steam.stats.requestGlobalStats(7);
const [today, yesterday] = steam.stats.getGlobalIntHistory('kills_total', 7);Returned by getAchievement and getUserAchievement.
| Field | Type | Meaning |
|---|---|---|
achieved |
boolean |
True once the user unlocked it. |
unlockTime |
number | null |
Unix seconds, or null while the achievement is locked. |
Returned by getDisplay. All three come from
GetAchievementDisplayAttribute.
| Field | Type | Attribute | Meaning |
|---|---|---|---|
name |
string |
name |
Localized display name. Empty when unconfigured or unknown. |
description |
string |
desc |
Localized description. Empty while a hidden achievement is locked. |
hidden |
boolean |
hidden |
True when Steam hides the achievement until it unlocks. The raw attribute is the string '1'. |
| Shape | When |
|---|---|
Error: steamwand: <call> returned false (invalid handle or argument?) |
A flat call returned false. For this interface that is almost always an API name the app does not define, or a name used with the wrong type (getInt on a FLOAT stat). <call> is the flat name: GetAchievement, SetStatInt32, StoreStats, and so on. |
Error: steamwand: GetNumberOfCurrentPlayers failed (is Steam online?) |
The player-count result came back with its success flag clear. |
SteamResultError |
RequestGlobalAchievementPercentages, RequestUserStats or RequestGlobalStats completed with a non-OK EResult. operation and result are on the error. |
SteamApiCallError |
One of the four call-result methods never produced a usable result, or completed carrying a different callback struct. See How It Works. |
There is no separate "stat does not exist" error class. A wrong API name and a
wrong type both surface as the same plain Error, so check the name against
listAchievements or the partner site when one appears.
The rest of ISteamUserStats is on the raw generated steam.userStats:
- Leaderboards. Those are wrapped separately, on Leaderboards.
-
GetGlobalStatHistoryDouble, the FLOAT counterpart of getGlobalIntHistory. -
GetMostAchievedAchievementInfoandGetNextMostAchievedAchievementInfo, the iterator form of the global percentages. -
GetAchievementProgressLimitsFloat, the FLOAT counterpart of getProgressLimits.
Those take raw Buffer out params and return flat booleans. Flat
API explains the calling convention.