Skip to content

Updating Achievement Progress

For "limited" achievements that require multiple completions (e.g., "Complete 10 multiballs"), the SDK tracks progress toward the target count. This page covers how to update and monitor achievement progress.

Understanding Limited Achievements

Limited achievements have a count greater than 1 and require incremental progress:

Property Description
count Target number of completions required
input_time Set to "limited" for count-based achievements

Example: An achievement "Multiball Master" with count: 10 requires the player to start 10 multiballs.

Incrementing Progress Locally

The SDK provides local progress tracking that can be used for UI updates:

// Increment progress for an achievement
bool success = sb_increment_achievement_progress(
    gs,
    "multiball_master",  // achievement key
    user_id,
    1                    // increment amount
);

if (success) {
    printf("Progress incremented\n");
}
// Increment progress for an achievement
bool success = gs.incrementProgress("multiball_master", userId, 1);

if (success) {
    std::cout << "Progress incremented\n";
}

// Increment by more than 1
gs.incrementProgress("targets_hit", userId, 5);  // Hit 5 targets at once
# Increment progress for an achievement
success = gs.increment_progress("multiball_master", user_id, 1)

if success:
    print("Progress incremented")

# Increment by more than 1
gs.increment_progress("targets_hit", user_id, 5)  # Hit 5 targets at once

Local Progress Tracking

incrementProgress() updates the local cache for UI purposes. The server maintains authoritative progress and will send updates via the progress event.

Receiving Progress Updates

The server sends progress update events when achievement progress changes:

void on_achievement_event(const sb_event_t* event, void* user_data) {
    sb_event_type_t type = sb_event_type(event);

    if (type == SB_EVT_ACHIEVEMENT_PROGRESS) {
        const char *key = NULL;
        const char *name = NULL;
        const char *user_id = NULL;
        const char *username = NULL;
        const char *icon_url = NULL;
        int current_value = 0;
        int target_value = 0;

        if (sb_event_achievement_progress(event, &key, &name, &user_id,
                                          &username, &icon_url,
                                          &current_value, &target_value)) {
            printf("Progress update for: %s\n", name ? name : "");
            printf("  Current: %d / %d\n", current_value, target_value);
            printf("  User: %s\n", username ? username : "");

            // Update progress bar or counter
            update_progress_display(key, current_value, target_value);
        }
    }
}
void onEvent(const scorbit::Event& event) {
    if (event.type() == scorbit::EventType::AchievementProgress) {
        std::string key, name, userId, username, iconUrl;
        int currentValue = 0, targetValue = 0;

        if (event.getAchievementProgress(key, name, userId, username, iconUrl,
                                         currentValue, targetValue)) {
            std::cout << "Progress update for: " << name << "\n";
            std::cout << "  Current: " << currentValue << " / " << targetValue << "\n";
            std::cout << "  User: " << username << "\n";

            // Update progress bar or counter
            updateProgressDisplay(key, currentValue, targetValue);
        }
    }
}
def on_event(event):
    if event.type() == scorbit.EventType.AchievementProgress:
        success, key, name, user_id, username, icon_url, current_value, target_value = \
            event.get_achievement_progress()

        if success:
            print(f"Progress update for: {name}")
            print(f"  Current: {current_value} / {target_value}")
            print(f"  User: {username}")

            # Update progress bar or counter
            update_progress_display(key, current_value, target_value)

Progress Event Data

Field Type Description
achievement_key string Unique achievement identifier
achievement_name string Display name
user_id int64 User's ID
username string User's display name
current_value int Current progress count
target_value int Target count for completion
icon_url string Achievement badge URL

Reading Cached Progress

Query the current progress for a user:

// Get progress for a specific achievement
sb_achievement_progress_t progress;
if (sb_get_cached_progress(gs, user_id, "multiball_master", &progress)) {
    printf("Progress: %d / target\n", progress.progress);
    printf("Unlocked: %s\n", progress.unlocked ? "yes" : "no");
    if (progress.unlocked && progress.unlocked_at[0] != '\0') {
        printf("Unlocked at: %s\n", progress.unlocked_at);
    }
}
// Get progress for a specific achievement
auto progress = gs.getProgress(userId, "multiball_master");
if (progress) {
    std::cout << "Progress: " << progress->progress << "\n";
    std::cout << "Unlocked: " << (progress->unlocked ? "yes" : "no") << "\n";
    if (progress->unlocked && !progress->unlockedAt.empty()) {
        std::cout << "Unlocked at: " << progress->unlockedAt << "\n";
    }
}

// Get all progress for a user
auto allProgress = gs.getUserProgress(userId);
if (allProgress) {
    for (const auto& p : *allProgress) {
        std::cout << p.key << ": " << p.progress << "\n";
    }
}
# Get progress for a specific achievement
progress = gs.get_progress(user_id, "multiball_master")
if progress:
    print(f"Progress: {progress.progress}")
    print(f"Unlocked: {'yes' if progress.unlocked else 'no'}")
    if progress.unlocked and progress.unlocked_at:
        print(f"Unlocked at: {progress.unlocked_at}")

# Get all progress for a user
all_progress = gs.get_user_progress(user_id)
if all_progress:
    for p in all_progress:
        print(f"{p.key}: {p.progress}")

Progress Data Structure

Field Type Description
key string Achievement key
progress int Current progress count
unlocked bool Whether achievement is unlocked
unlocked_at string ISO timestamp when unlocked (if unlocked)

Displaying Progress

void display_achievement_progress(sb_game_handle_t gs, int64_t user_id,
                                  const char *key) {
    sb_achievement_t ach;
    if (!sb_get_cached_achievement(gs, key, &ach)) return;

    sb_achievement_progress_t prog;
    bool has_prog = sb_get_cached_progress(gs, user_id, key, &prog);

    printf("%s\n", ach.name);
    if (has_prog && prog.unlocked) {
        printf("  ✓ UNLOCKED\n");
    } else if (ach.count > 1) {
        int current = has_prog ? prog.progress : 0;
        printf("  Progress: %d/%d (%d%%)\n", current, ach.count,
               ach.count > 0 ? current * 100 / ach.count : 0);
    } else {
        printf("  ○ Not yet unlocked\n");
    }
}
void displayAchievementProgress(
    scorbit::GameState& gs,
    int64_t userId,
    const std::string& key
) {
    auto achievement = gs.getAchievement(key);
    auto progress = gs.getProgress(userId, key);

    if (!achievement) return;

    std::cout << achievement->name << "\n";

    if (progress && progress->unlocked) {
        std::cout << "  ✓ UNLOCKED\n";
    } else if (achievement->count > 1) {
        int current = progress ? progress->progress : 0;
        int target = achievement->count;
        float percent = (float)current / target * 100;

        std::cout << "  Progress: " << current << "/" << target
                  << " (" << (int)percent << "%)\n";

        // Draw progress bar
        int barWidth = 20;
        int filled = (int)(percent / 100 * barWidth);
        std::cout << "  [";
        for (int i = 0; i < barWidth; i++) {
            std::cout << (i < filled ? "█" : "░");
        }
        std::cout << "]\n";
    } else {
        std::cout << "  ○ Not yet unlocked\n";
    }
}
def display_achievement_progress(gs, user_id, key):
    achievement = gs.get_achievement(key)
    progress = gs.get_progress(user_id, key)

    if not achievement:
        return

    print(achievement.name)

    if progress and progress.unlocked:
        print("  ✓ UNLOCKED")
    elif achievement.count > 1:
        current = progress.progress if progress else 0
        target = achievement.count
        percent = current / target * 100

        print(f"  Progress: {current}/{target} ({int(percent)}%)")

        # Draw progress bar
        bar_width = 20
        filled = int(percent / 100 * bar_width)
        bar = "█" * filled + "░" * (bar_width - filled)
        print(f"  [{bar}]")
    else:
        print("  ○ Not yet unlocked")

Best Practices

1. Sync Local Progress with Server Events

void onEvent(const scorbit::Event& event) {
    if (event.type() == scorbit::EventType::AchievementProgress) {
        std::string key, name, userId, username, iconUrl;
        int currentValue, targetValue;

        if (event.getAchievementProgress(key, name, userId, username, iconUrl,
                                         currentValue, targetValue)) {
            // Server progress is authoritative - update local cache
            // The SDK automatically updates the cache from events
            // Just update your UI
            refreshProgressUI(key);
        }
    }
}

2. Handle Progress-to-Unlock Transition

When progress reaches the target, you'll receive both a progress event and an unlock event:

// Progress event: currentValue == targetValue
// Then: Unlock event for the same achievement
// Handle both gracefully - don't show duplicate notifications

3. Show Meaningful Progress

Only show progress bars for limited achievements where progress is meaningful:

if (achievement->inputTime == "limited" && achievement->count > 1) {
    showProgressBar(current, target);
} else {
    showLockedOrUnlockedState();
}