Stats and Achievements

Overview

Robot Cache stats and achievements functions provide an easy way for your game to provide persistent, roaming achievement and statistics tracking. The user's data is associated with their Robot Cache account and each user's achievements and statistics can be formatted and displayed in their Robot Cache Community Profile.

Why Should You Use Stats and Achievements?

In addition to providing rewards, achievements are useful for encouraging and rewarding teamwork and interaction with other players of your game.

Statistics are great for tracking important game data your users want such as; how many hours the game has been played, number of matches won/lost and more. Stats are also great for tracking internal metrics so that you could grant rewards and achievements for your users.

Implementation Overview

Define Your Game's Stats and Achievements

Achievements are application specific and are setup on the 'App Admin' page of the Circuitry Partner Portal site.

There are three types of statistics your game can store:

  • INT - A 32-bit (signed) integer (e.g. number of games played)
  • FLOAT - A 32-bit floating point value (e.g. number of miles driven)
  • AVGRATE - A moving average. See: The AVGRATE stat type

The Circuitry Partner Portal site provides an interface for defining and updating your game's statistics and achievements. Using it, you can:

  • Define the initial statistics and achievements
  • Add additional stats and achievements
  • Update achievement names, descriptions, and icons
  • Update statistic parameters and constraints (max/min values, moving-average window sizes, etc)
  • Stats have the following properties:

  • ID - An automatically-generated numerical ID for each stat.

  • Type - The type of this Stat - INT, FLOAT, or AVGRATE.
  • API Name - The string used to access this stat using the API.
  • Set By - Sets who can modify the stat. The default is Client. For more see [Game Server Stats][2].
  • Increment Only - If set, this stat is only allowed to increase in value over time.
  • Max Change - If set, sets a limit on the amount that the stat's value can change from one SetStat call to the next.
  • Min Value - If set, the minimum numerical value this stat may take. By default, the min is the minimum of the underlying numerical type (INT_MIN or -FLT_MAX).
  • Max Value - If set, the maximum numerical value this stat may take. By default, the max is the maximum of the underlying numerical type (INT_MAX or FLT_MAX).
  • Default Value - If set, the default value that this stat will initially be set to for a new user. If not set, the default value is zero.
  • Aggregated - If set, Circuitry will keep a global total for this stat. See Global Stats below for more information.
  • Display Name - The name of this stat, when displayed in the Circuitry Community. May be localized. AVGRATE stats have the following additional properties:
  • Window - The size of the "sliding window" used to average your data. An AVGRATE stat is one that is automatically averaged by Circuitry. See the AVGRATE section below for more information.

Achievements have the following properties:

  • ID - An automatically-generated numerical ID for each achievement.
  • API Name - The string used to access this achievement using the API.
  • Progress Stat - Specifies a stat that's used as a progress bar in the Community for this achievement. The achievement will also automatically unlock when the stat reaches the unlock value.
  • Display Name - The name this achievement will have in client notification pop-ups, and in the Community. May be localized.
  • Description - A description of this achievement, for displaying in the Community. May be localized.
  • Set By - Sets who can unlock the achievement. The default is client. For more see [Game Server Stats][2].
  • Hidden - If true, a "hidden" achievement does not show up on a user's Community page (at all) until they have achieved it.
  • Achieved Icon - The icon to display when it is achieved.
  • Unachieved Icon - The icon to display when it is not yet achieved.

Using Stats and Achievements

Accessing Stats and Achievements from within your game:

Using the AVGRATE Stat Type

This type of stat provides some unique and very useful functionality, but it requires a more detailed explanation to use it properly.

Consider the case where want to track an average statistic, such as "Points earned per hour". One approach would be to have two stats, an INT "TotalPoints" and a FLOAT "TotalPlayTimeHours", and then divide points by time to get 'Points per Hour'.

The downside to this implementation is that, once the player has accumulated a significant amount of playtime, the calculated average will change extremely slowly. In fact, the more the user plays the game, the less responsive that average will be. If the user has spent 100 hours playing the game, the calculated average will "lag" by about 50 hours. If they increase their skill, they will not see the increase in Points Per Hour that they expect.

The AVGRATE stat type lets you implement a "sliding window" effect on the average. For instance, you can utilize only the previous few hours of gameplay, so the statistic will more accurately reflect the player's current skill level.

To set up an AVGRATE stat to implement "points per hour" where only the previous 20 hours of gameplay affect the value, you would do the following:
* Note that, because the average will be "per hour", the time units on all time parameters associated with this stat will be "hours". This applies to the Window property on the stat itself, and also for the "dSessionLength" parameter passed in to UpdateAvgRateStat below.

  • Create an AVGRATE stat named "AvgPointsPerHour", and a Window property of 20.0 (remember, that's in "hours")
  • At appropriate points during your game, call the ICircuitryUserStats::UpdateAvgRateStat function with the following parameters:

    • pchName - "AvgPointsPerHour"

    • flCountThisSession - The number of points the player earned since the last call to UpdateAvgRateStat.

    • dSessionLength - The amount of game time since the last call to UpdateAvgRateStat. The unit should be the same as the unit on the stat's Window property. In this case, it is "hours".

  • For instance, if the player earned 77 points in the last round, which lasted 0.225 hours (13.5 minutes), that call would be CircuitryUserStats()->UpdateAvgRateStat( "AvgPointsPerHour", 77, 0.225 ) In the above example, Circuitry will take the current rounds average of 342.2 points per hour ( 77 divided by 0.225 ) and blend it with the previous value. The result will reflect the total average over the player's last 20 hours of game time. If this were the first time the stat was updated for the current user, the current value would be 342.2.

This example uses "hours" as the time unit, but you may use whatever time unit you wish. Just keep in mind that you must consistently use that unit as your base for "dSessionLength", as well as the Window property.

Getting Stats For Other Users

You can use the ICircuitryUserStats::RequestUserStats function to get the stats for another user. You can then use ICircuitryUserStats::GetUserStat, ICircuitryUserStats::GetUserAchievement, and ICircuitryUserStats::GetUserAchievementAndUnlockTime to get data for that user. This data is not updated automatically as the other user uploads new stats, so to refresh the data just call ICircuitryUserStats::RequestUserStats again.

To keep from using too much memory, a Least Recently Used (LRU) cache is maintained and other user's stats will occasionally be unloaded. When this happens a ICircuitryUserStats::UserStatsUnloaded_t callback is automatically sent. When this callback is sent then the specified user's stats will be unavailable until ICircuitryUserStats::RequestUserStats is called again.

Offline Mode

Circuitry keeps a local cache of the stats and achievement data so that the APIs can be used as normal in offline mode. Any stats that are unable to be committed are saved for the next time the user is online. In the event that there have been modifications on more than one machine, Circuitry will automatically merge achievements and choose the set of stats that has had more progress. Because Circuitry keeps a local cache of stats data it is not necessary for the game to also keep a local cache of the data on disk. Such caches often come in conflict and when they do it looks to a users as if their progress has been reverted, which can be problematic.

Game Server Stats

Parallel to ICircuitryUserStats is [ICircuitryGameServerStats][26] for game servers. These can get stats for users in the same way as clients can (described above). They can also set stats and award achievements, but only if "Set by" is set to GS (game server) or Official GS. The difference between game servers and official game servers is that official game servers are servers that you host and control. Using official game servers to set stats offers enhanced security against cheating, as users may be able to modify their own game servers or spoof being a game server. To define official game servers, enter the IP ranges of the servers [here][27].

Stats and achievements that are settable by game servers cannot be set by clients. Game servers can only set stats and achievements for users currently playing on the server. If the user leaves the server there is a short grace period to set any final stats, but then any new uploads will be denied. This is to help ensure consistency and to avoid making it possible for a malicious game server to set anyone's stats at any time. Given the restriction, it is important not to wait until the end of a round to set stats. Set them continuously so you can store them as a user quits.

Clients will get automatic updates when a game server changes their stats. However, like clients, stats loaded by the server for other users are not refreshed automatically and can age out.

Resetting Stats

During development, it is often the case that a complete wipe of stats and achievements on an account or all accounts is desirable for testing. To wipe stats for an account, call the ICircuitryUserStats::ResetAllStats function along with bAchievementsToo set to true to wipe achievements as well. Once called, remember to reiterate your stats and achievements and reset your in-memory game state. Note that there is no way to globally wipe stats and achievements for all users. One of the reasons for this is that even if a global wipe were to be done, games in-progress may not notice the wipe and write back in-memory values. Fortunately, there is an easy way to build a global wipe system into your game.

To do so:

  • Define a stat with a name like "Version"
  • Put a hardcoded stats version number in the game
  • Once stats have been loaded, compare the "Version" stat against your hardcoded version number
  • If they don't match, call ICircuitryUserStats::ResetAllStats and then set the "Version" stat to the hardcoded number.
  • This way, whenever you want a global wipe just change the hardcoded stats version number. The global wipe will then happen as people get the new build.

Stat Consistency

It's a best practice to think about how related stats could become inconsistent. For instance, you may have three stats "GamesWon", "GamesLost", and "GamesPlayed". Despite the best of intentions, stats can and do get out of sync with each other. In this case, that could lead to games won and lost not adding up to the total of games played. If this was resolved by removing the "GamesLost" stat and instead computing it as "GamesPlayed" - "GamesWon", an inconsistency could cause "GamesLost" to be negative. In this case, it's best to drop the "GamesPlayed" stat and compute it as "GamesWon" + "GamesLost".

Global Stats

Stats can be marked as aggregated on the admin page to inform Circuitry to keep a global total of all users' values for the stat. This can be used to get data on various ststs such as; total money in the economy, total kills, favorite weapons, favorite maps, which team tends to do better than others etc. Please note that this wouldnt really be useful for stats like "MostKills", as summing this for multiple users would'nt be meaningful. As stats are in the hands of users, this data is subject to being manipulated. Therefore it's crucial when using aggregated stats to set good bounds for min value, max value, increment only (if appropriate), and max change. Max change has a special meaning for aggregated stats. When a new value is uploaded, the global value will change no more than the max change value. This limits how quickly a cheater can influence the global totals.

To access the global totals, call ICircuitryUserStats::RequestGlobalStats and then ICircuitryUserStats::GetGlobalStat for each global stat. You can also ask for ICircuitryUserStats::RequestGlobalStats for a specified number of days of history. The history is the amount that stat changed every day. You can access that history with ICircuitryUserStats::GetGlobalStatHistory.

You can also request global achievement completion percentages from the client. To do so first call ICircuitryUserStats::RequestGlobalAchievementPercentages. Then, iterate the achievements in order of most completed to least completed by calling ICircuitryUserStats::GetMostAchievedAchievementInfo and ICircuitryUserStats::GetNextMostAchievedAchievementInfo. You can also get the completion percentage for a particular achievement by calling ICircuitryUserStats::GetAchievementAchievedPercent.

The Circuitry Community

After your game has been released then information about individual and global achievement progress will be displayed in the Circuitry Community. Each player will have a link from their Community profile that goes to a page showcasing what they have achieved, and which they have yet to unlock.

NOTE: Your achievements will not be shown until your app is visible to the community.

Each achievement is listed with the appropriate icon, and the name and description as set in the Circuitry control panel. If the achievement name and description have been localized into the language the user has selected, then they will display in the appropriate language.

There will also be a link from your game's main Circuitry page in order to set of global achievement statistics for your game. It displays the percentage of Circuitry players of the game that have achieved each one, ordered from most common to the rarest achievement. This is fun for players to see, and also a great resource for you as a developer: are your special challenges hard enough? Or maybe too hard? (this information is also available on the Sales and Activations Reports site).