# Event Leaderboards

**URL:** https://heroiclabs.com/docs/hiro/unity/event-leaderboards/
**Keywords:** event leaderboards, hiro
**Categories:** hiro, unity, event-leaderboards

---


# Event Leaderboards

Read more about the Event Leaderboards system in Hiro [here](../../concepts/event-leaderboards/).

## Initialize the event leaderboards system

The event leaderboards system system relies on the [Nakama System](../getting-started/nakama-system) and an `ILogger`, both must be passed in as dependencies via the constructor.

```csharp
var eventLeaderboardsSystem = new EventLeaderboardsSystem(logger, nakamaSystem);
systems.Add(eventLeaderboardsSystem);
```

## Event leaderboard response

The `IEventLeaderboard` response object includes the following properties:

| Property                | Type   | Description                                                           |
| ----------------------- | ------ | --------------------------------------------------------------------- |
| `BestRank`              | `long` | Best rank achieved across cohorts this phase.                         |
| `CanClaim`              | `bool` | Whether the player can claim their reward.                            |
| `CanRoll`               | `bool` | Whether the player can roll into a new cohort right now.              |
| `CanRollWithClaim`      | `bool` | Whether the player can claim and roll in a single call.               |
| `CanUpdate`             | `bool` | Whether the player can submit scores to their current cohort.         |
| `Count`                 | `long` | Number of players currently in the cohort.                            |
| `IsActive`              | `bool` | Whether the event leaderboard's current phase is live.                |
| `MaxCount`              | `long` | Maximum number of players the cohort can hold.                        |
| `MaxRolls`              | `long` | Maximum rolls allowed per phase.                                      |
| `RerollCountFreeze`     | `bool` | Whether the next roll is "free" (does not count against `MaxRolls`).  |
| `RollCooldownSec`       | `long` | Effective cooldown between rolls, in seconds.                         |
| `Rolls`                 | `long` | Number of rolls the player has used this phase.                       |
| `ScoreTarget`           | `long` | Target score for cohort completion.                                   |
| `ScoreTargetPlayers`    | `long` | How many players must reach the target.                               |
| `Tier`                  | `int`  | The player's current tier (0-indexed).                                |
| `TierDeltaPerPhase`     | `long` | Tier adjustment applied when a phase expires.                         |
| `ClaimTimeSec`          | `long` | Unix timestamp when the player claimed their reward (0 if unclaimed). |
| `EndTimeSec`            | `long` | Unix timestamp when the player's current cohort is scheduled to end.  |
| `ExpiryTimeSec`         | `long` | Unix timestamp when the player's phase pointer expires.               |
| `RollCooldownEndsSec`   | `long` | When the current cooldown expires (UNIX timestamp).                   |
| `ScoreTimeLimitEndsSec` | `long` | When this player's time limit expires (UNIX timestamp).               |
| `ScoreTimeLimitSec`     | `long` | Per-player time limit from roll time, in seconds.                     |
| `StartTimeSec`          | `long` | Unix timestamp when the player's current cohort started.              |

## Score entry properties

`IEventLeaderboard.Scores` is a collection of `IEventLeaderboardScore` objects, one per player in the cohort.

| Property | Type | Description |
| -------- | ---- | ----------- |
| `Id` | `string` | User ID of the player. |
| `Username` | `string` | Username of the player. |
| `DisplayName` | `string` | Display name of the player. |
| `AvatarUrl` | `string` | Avatar URL of the player. |
| `Rank` | `long` | The player's rank within the cohort. `0` means the player hasn't submitted a score yet. |
| `Score` | `long` | The player's primary score. |
| `Subscore` | `long` | The player's secondary score. |
| `NumScores` | `long` | Number of score submissions by this player. |
| `TierDelta` | `int` | Predicted tier change if the current phase ended now.<br>Positive: promotion (value = tiers gained)<br>Negative: demotion (value = tiers lost)<br>`0`: no change |
| `Metadata` | `string` | Custom metadata attached to this score entry. |
| `CreateTimeSec` | `long` | Unix timestamp when the player joined this cohort. |
| `UpdateTimeSec` | `long` | Unix timestamp of the player's last score submission. |

Use `TierDelta` to show players whether they're on track for promotion, demotion, or no change, while the event is still live.

```csharp
foreach (var score in eventLeaderboard.Scores)
{
    var tierStatus = score.TierDelta switch
    {
        > 0 => $"▲ Promotion (+{score.TierDelta})",
        < 0 => $"▼ Demotion ({score.TierDelta})",
        _   => "— No change"
    };

    Debug.Log($"#{score.Rank} {score.Username}: {score.Score} — {tierStatus}");
}
```

## Determine event leaderboard state

When the server returns an Event Leaderboard, its current state is represented by a combination of boolean properties. Check if the player can claim rewards and roll into a new cohort simultaneously by reading the `CanRollWithClaim` property in the response.

{{< table name="gdk.concepts.event-leaderboards.event-leaderboard-state">}}

## Subscribe to changes in the event leaderboards system

You can listen for changes in the event leaderboards system so that you can respond appropriately, such as updating the UI, by implementing the `IObserver` pattern, or use the `SystemObserver<T>` type which handles it for you.

```csharp
var disposer = SystemObserver<EventLeaderboardsSystem>.Create(eventLeaderboardsSystem, system => {
    Instance.Logger.Info($"System updated.");

    // Update UI elements etc as necessary here...
});
```

## List event leaderboards

You can list all the available event leaderboards to the player by passing `null` as an argument.

```csharp
var eventLeaderboards = await eventSystem.ListEventLeaderboardsAsync(null);
```

You can filter down the event leaderboards to only include those that belong to at least one of the given categories.

```csharp
IEnumerable<string> categories = new List<string> { "level_completion", "race"};
var eventLeaderboards = await eventSystem.ListEventLeaderboardsAsync(categories);
```

By default, the response doesn't include the scores of the event leaderboards. To have the response returning with the scores, set the boolean `with_score` on the request to true.

```csharp
var eventLeaderboards = await eventSystem.ListEventLeaderboardsAsync(null, true);
```

## Get an individual event leaderboard

You can get an individual event leaderboard, including it's records and information on it's rewards.

```csharp
var eventLeaderboard = await eventLeaderboardsSystem.GetEventLeaderboardAsync("<leaderboardId>");
```

## Submit an event leaderboard score

You can submit an event leaderboard score for the user. Check `CanUpdate` on the response to determine whether the player can still submit scores to their current cohort.

```csharp
var score = 100;
var subscore = 10;
var eventLeaderboard = await eventLeaderboardsSystem.UpdateEventLeaderboardAsync("<leaderboardId>", score, subscore);

// Check if further score submissions are allowed
if (!eventLeaderboard.CanUpdate)
{
    // The player can no longer submit scores to this cohort
}
```

## Claim rewards

You can claim event leaderboard rewards for the user.

```csharp
var eventLeaderboard = await eventLeaderboardsSystem.ClaimEventLeaderboardAsync("<leaderboardId>");
```

## Roll into a cohort

You can roll (or re-roll) the user into a cohort for a specific event leaderboard. The first roll in a phase places the player into a cohort for the first time. Subsequent rolls within the same phase are re-rolls, where the player leaves a completed cohort and joins a new one with fresh opponents.

```csharp
// Roll into a cohort (first roll or re-roll after claiming separately)
var eventLeaderboard = await eventLeaderboardsSystem.RollEventLeaderboardAsync("<leaderboardId>");
```

After rolling, `eventLeaderboard.Count` and `eventLeaderboard.MaxCount` show how many players are currently in the cohort and the maximum it can hold.

```csharp
Debug.Log($"Cohort: {eventLeaderboard.Count}/{eventLeaderboard.MaxCount} players");
```

To claim the current reward and roll into a new cohort in a single call, pass `claimReward: true`. This is the recommended approach for re-rolling as it avoids an extra round-trip.

```csharp
// Claim reward and roll into a new cohort in one call
var eventLeaderboard = await eventLeaderboardsSystem.RollEventLeaderboardAsync("<leaderboardId>", claimReward: true);
```

### Multi-roll game loop

For score-based events where players can compete in multiple cohorts within a single phase, the typical flow looks like this:

```csharp
// 1. Roll into the first cohort
var el = await eventLeaderboardsSystem.RollEventLeaderboardAsync("score_rush");

// 2. Submit scores while the cohort is active
while (el.CanUpdate)
{
    el = await eventLeaderboardsSystem.UpdateEventLeaderboardAsync("score_rush", playerScore);
}

// 3. Check if can the player can roll into a cohort and claim rewards
if (el.CanRollWithClaim || el.CanRoll)
{
   // Claim and roll in one call
    el = await eventLeaderboardsSystem.RollEventLeaderboardAsync("score_rush", claimReward: el.CanRollWithClaim);
}
else if (el.CanClaim)
{
    // Just claim, done for this phase (or waiting for cooldown)
    el = await eventLeaderboardsSystem.ClaimEventLeaderboardAsync("score_rush");
}
```

## Debug an event leaderboard

You can fill an event leaderboard with dummy users and assign random scores to them for testing purposes.

{{< note "warning" >}}
This is intended for debugging use only.
{{< / note >}}

```csharp
var leaderboardId = "<leaderboardId>";
var targetCount = 50; // Optional target cohort size to fill to, otherwise fill to the max cohort size.

var minScore = 1;
var maxScore = 100;
var @operator = ApiOperator.SET;
var subscoreMin = 1;
var subscoreMax = 100;

// Fills cohort with debug players
await eventLeaderboardsSystem.DebugFillAsync(leaderboardId, targetCount);

// Sets randomly generated scores between a range for other players (does not change the user's score)
await eventLeaderboardsSystem.DebugRandomScoresAsync(leaderboardId, minScore, maxScore, @operator, subscoreMin, subscoreMax);

// Removes the user from their current cohort, allowing re-enrollment
await eventLeaderboardsSystem.DebugUnenrollAsync(leaderboardId);
```

## Additional information

- [Hiro Event Leaderboards sample project](../../../../sample-projects/unity/hiro-event-leaderboards)
