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:
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,
¤t_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: