Achievement Status Management¶
This page covers how to query and manage the unlock status of achievements for users, including checking if achievements are unlocked and handling status changes.
Checking Unlock Status¶
Query whether a specific achievement is unlocked for a user:
// Check if achievement is unlocked
sb_achievement_progress_t progress;
if (sb_get_cached_progress(gs, user_id, "wizard_mode_complete", &progress)) {
if (progress.unlocked) {
printf("Achievement is unlocked!\n");
printf("Unlocked at: %s\n", progress.unlocked_at);
} else {
printf("Achievement not yet unlocked\n");
printf("Current progress: %d\n", progress.progress);
}
} else {
printf("No progress data for this user/achievement\n");
}
// Check if achievement is unlocked
auto progress = gs.getProgress(userId, "wizard_mode_complete");
if (progress) {
if (progress->unlocked) {
std::cout << "Achievement is unlocked!\n";
std::cout << "Unlocked at: " << progress->unlockedAt << "\n";
} else {
std::cout << "Achievement not yet unlocked\n";
std::cout << "Current progress: " << progress->progress << "\n";
}
} else {
std::cout << "No progress data for this user/achievement\n";
}
# Check if achievement is unlocked
progress = gs.get_progress(user_id, "wizard_mode_complete")
if progress:
if progress.unlocked:
print("Achievement is unlocked!")
print(f"Unlocked at: {progress.unlocked_at}")
else:
print("Achievement not yet unlocked")
print(f"Current progress: {progress.progress}")
else:
print("No progress data for this user/achievement")
Getting All User Achievements¶
Retrieve the complete achievement status for a user:
// Count unlocked achievements by iterating all cached achievements
void display_user_summary(sb_game_handle_t gs, int64_t user_id) {
size_t total = sb_get_cached_achievements_count(gs);
int unlocked = 0, with_progress = 0;
for (size_t i = 0; i < total; i++) {
sb_achievement_t ach;
if (!sb_get_cached_achievement_at(gs, i, &ach)) continue;
sb_achievement_progress_t prog;
if (sb_get_cached_progress(gs, user_id, ach.key, &prog)) {
with_progress++;
if (prog.unlocked) unlocked++;
}
}
printf("Achievements: %d/%d unlocked\n", unlocked, with_progress);
}
// Get all achievement progress for a user
auto allProgress = gs.getUserProgress(userId);
if (allProgress) {
int unlocked = 0;
int total = allProgress->size();
for (const auto& progress : *allProgress) {
if (progress.unlocked) {
unlocked++;
}
}
std::cout << "Achievements: " << unlocked << "/" << total << " unlocked\n";
}
Categorizing Achievement Status¶
Organize achievements by their current status:
void display_categorized(sb_game_handle_t gs, int64_t user_id) {
size_t total = sb_get_cached_achievements_count(gs);
int unlocked = 0, in_progress = 0, locked = 0;
for (size_t i = 0; i < total; i++) {
sb_achievement_t ach;
if (!sb_get_cached_achievement_at(gs, i, &ach)) continue;
sb_achievement_progress_t prog;
if (sb_get_cached_progress(gs, user_id, ach.key, &prog)) {
if (prog.unlocked) unlocked++;
else if (prog.progress > 0) in_progress++;
else locked++;
} else {
locked++;
}
}
printf("Unlocked: %d, In Progress: %d, Locked: %d\n",
unlocked, in_progress, locked);
}
struct AchievementsByStatus {
std::vector<scorbit::Achievement> unlocked;
std::vector<scorbit::Achievement> inProgress;
std::vector<scorbit::Achievement> locked;
};
AchievementsByStatus categorizeAchievements(
scorbit::GameState& gs,
int64_t userId
) {
AchievementsByStatus result;
auto achievements = gs.getAchievements();
for (const auto& ach : achievements) {
auto progress = gs.getProgress(userId, ach.key);
if (progress && progress->unlocked) {
result.unlocked.push_back(ach);
} else if (progress && progress->progress > 0) {
result.inProgress.push_back(ach);
} else {
result.locked.push_back(ach);
}
}
return result;
}
// Usage
auto status = categorizeAchievements(gs, userId);
std::cout << "Unlocked: " << status.unlocked.size() << "\n";
std::cout << "In Progress: " << status.inProgress.size() << "\n";
std::cout << "Locked: " << status.locked.size() << "\n";
def categorize_achievements(gs, user_id):
"""Categorize achievements by status."""
achievements = gs.get_achievements()
unlocked = []
in_progress = []
locked = []
for ach in achievements:
progress = gs.get_progress(user_id, ach.key)
if progress and progress.unlocked:
unlocked.append(ach)
elif progress and progress.progress > 0:
in_progress.append(ach)
else:
locked.append(ach)
return {
'unlocked': unlocked,
'in_progress': in_progress,
'locked': locked
}
# Usage
status = categorize_achievements(gs, user_id)
print(f"Unlocked: {len(status['unlocked'])}")
print(f"In Progress: {len(status['in_progress'])}")
print(f"Locked: {len(status['locked'])}")
Handling Status Changes¶
Register a callback to handle all achievement status changes:
void on_achievement_event(const sb_event_t* event, void* user_data) {
sb_event_type_t type = sb_event_type(event);
const char *key = NULL;
const char *name = NULL;
const char *user_id = NULL;
const char *username = NULL;
const char *icon_url = NULL;
switch (type) {
case SB_EVT_ACHIEVEMENT_UNLOCKED: {
bool is_trophy = false;
if (sb_event_achievement_unlocked(event, &key, &name, &user_id,
&username, &icon_url, &is_trophy)) {
// Update UI: show as unlocked
set_achievement_unlocked(key, user_id, true);
play_unlock_animation(key);
}
break;
}
case SB_EVT_ACHIEVEMENT_LOCKED: {
if (sb_event_achievement_locked(event, &key, &name, &user_id,
&username, &icon_url)) {
// Update UI: show as locked (trophy revoked)
set_achievement_unlocked(key, user_id, false);
show_trophy_revoked_notification(name);
}
break;
}
case SB_EVT_ACHIEVEMENT_PROGRESS: {
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)) {
// Update UI: refresh progress display
update_achievement_progress(key, user_id, current_value, target_value);
}
break;
}
}
}
void onEvent(const scorbit::Event& event) {
std::string key, name, userId, username, iconUrl;
switch (event.type()) {
case scorbit::EventType::AchievementUnlocked: {
bool isTrophy = false;
if (event.getAchievementUnlocked(key, name, userId, username, iconUrl, isTrophy)) {
// Update UI: show as unlocked
setAchievementUnlocked(key, userId, true);
playUnlockAnimation(key);
}
break;
}
case scorbit::EventType::AchievementLocked: {
if (event.getAchievementLocked(key, name, userId, username, iconUrl)) {
// Update UI: show as locked (trophy revoked)
setAchievementUnlocked(key, userId, false);
showTrophyRevokedNotification(name);
}
break;
}
case scorbit::EventType::AchievementProgress: {
int currentValue = 0, targetValue = 0;
if (event.getAchievementProgress(key, name, userId, username, iconUrl,
currentValue, targetValue)) {
// Update UI: refresh progress display
updateAchievementProgress(key, userId, currentValue, targetValue);
}
break;
}
default:
break;
}
}
def on_event(event):
if event.type() == scorbit.EventType.AchievementUnlocked:
success, key, name, user_id, username, icon_url, is_trophy = \
event.get_achievement_unlocked()
if success:
# Update UI: show as unlocked
set_achievement_unlocked(key, user_id, True)
play_unlock_animation(key)
elif event.type() == scorbit.EventType.AchievementLocked:
success, key, name, user_id, username, icon_url = \
event.get_achievement_locked()
if success:
# Update UI: show as locked (trophy revoked)
set_achievement_unlocked(key, user_id, False)
show_trophy_revoked_notification(name)
elif event.type() == scorbit.EventType.AchievementProgress:
success, key, name, user_id, username, icon_url, current_value, target_value = \
event.get_achievement_progress()
if success:
# Update UI: refresh progress display
update_achievement_progress(key, user_id, current_value, target_value)
Using Triggered Callbacks¶
For simplified status change handling, use the triggered callback:
// Callback for achievement status changes
void on_achievement_triggered(
const char* key,
int64_t user_id,
bool unlocked,
int progress,
void* user_data
) {
if (unlocked) {
printf("Achievement %s unlocked for user %lld\n", key, user_id);
} else {
printf("Achievement %s progress: %d for user %lld\n", key, progress, user_id);
}
}
// Register the callback
sb_set_achievement_triggered_callback(gs, on_achievement_triggered, NULL);
// Register a simplified callback
gs.setAchievementTriggeredCallback(
[](const std::string& key, int64_t userId, bool unlocked, int progress) {
if (unlocked) {
std::cout << "Achievement " << key << " unlocked for user " << userId << "\n";
} else {
std::cout << "Achievement " << key << " progress: " << progress
<< " for user " << userId << "\n";
}
}
);
Status Display Helper¶
Create a comprehensive status display:
void display_achievement_status(sb_game_handle_t gs, int64_t user_id,
const char *key) {
sb_achievement_t ach;
if (!sb_get_cached_achievement(gs, key, &ach)) {
printf("Achievement not found: %s\n", key);
return;
}
sb_achievement_progress_t prog;
bool has = sb_get_cached_progress(gs, user_id, key, &prog);
// Handle obscured
const char *name = ach.name;
const char *desc = ach.description;
if (ach.obscure && !(has && prog.unlocked)) {
name = "???";
desc = "Hidden achievement";
}
printf("%s\n %s\n", name, desc);
if (has && prog.unlocked)
printf(" Status: ✓ UNLOCKED\n");
else if (has && ach.count > 1 && prog.progress > 0)
printf(" Status: In Progress (%d/%d)\n", prog.progress, ach.count);
else
printf(" Status: Locked\n");
if (ach.is_trophy)
printf(" Type: Trophy (can be revoked)\n");
}
void displayAchievementStatus(
scorbit::GameState& gs,
int64_t userId,
const std::string& key
) {
auto ach = gs.getAchievement(key);
auto progress = gs.getProgress(userId, key);
if (!ach) {
std::cout << "Achievement not found: " << key << "\n";
return;
}
// Handle obscured achievements
std::string displayName = ach->name;
std::string displayDesc = ach->description;
bool isObscured = ach->obscure;
if (isObscured && (!progress || !progress->unlocked)) {
displayName = "???";
displayDesc = "Hidden achievement";
}
std::cout << displayName << "\n";
std::cout << " " << displayDesc << "\n";
if (progress) {
if (progress->unlocked) {
std::cout << " Status: ✓ UNLOCKED\n";
std::cout << " Unlocked: " << progress->unlockedAt << "\n";
} else if (ach->count > 1) {
float pct = (float)progress->progress / ach->count * 100;
std::cout << " Status: In Progress\n";
std::cout << " Progress: " << progress->progress << "/"
<< ach->count << " (" << (int)pct << "%)\n";
} else {
std::cout << " Status: Locked\n";
}
} else {
std::cout << " Status: Locked (no progress)\n";
}
if (ach->isTrophy) {
std::cout << " Type: Trophy (can be revoked)\n";
}
}
def display_achievement_status(gs, user_id, key):
ach = gs.get_achievement(key)
if not ach:
print(f"Achievement not found: {key}")
return
progress = gs.get_progress(user_id, key)
is_unlocked = progress and progress.unlocked
is_obscured = ach.obscure and not is_unlocked
name = "???" if is_obscured else ach.name
desc = "Hidden achievement" if is_obscured else ach.description
print(f"{name}\n {desc}")
if is_unlocked:
print(f" Status: ✓ UNLOCKED")
elif progress and ach.count > 1 and progress.progress > 0:
pct = progress.progress * 100 // ach.count
print(f" Status: In Progress ({progress.progress}/{ach.count}, {pct}%)")
else:
print(" Status: Locked")
if ach.is_trophy:
print(" Type: Trophy (can be revoked)")
Best Practices¶
1. Cache Status for Quick Access¶
// Maintain a local status cache updated by events
std::unordered_map<std::string, bool> achievementStatus;
void onEvent(const scorbit::Event& event) {
std::string key, name, userId, username, iconUrl;
bool isTrophy;
if (event.type() == scorbit::EventType::AchievementUnlocked) {
if (event.getAchievementUnlocked(key, name, userId, username, iconUrl, isTrophy)) {
achievementStatus[key] = true;
}
} else if (event.type() == scorbit::EventType::AchievementLocked) {
if (event.getAchievementLocked(key, name, userId, username, iconUrl)) {
achievementStatus[key] = false;
}
}
}
2. Handle Unknown Users Gracefully¶
auto progress = gs.getProgress(userId, key);
if (!progress) {
// User has no progress data - treat as locked with 0 progress
showLockedState();
}