Achievement Groups¶
Achievement groups allow you to organize related achievements together for better display and categorization. This is useful for creating achievement tiers, collections, or thematic groupings.
Understanding Groups¶
A group is a collection of related achievements where:
- Each achievement can belong to at most one group
- Grouped achievements share a common
group_id - Groups are primarily for organizational and display purposes
Identifying Grouped Achievements¶
Check if an achievement belongs to a group:
Finding Group Members¶
To find all achievements in a specific group:
// Find all achievements in a group, returns count found
size_t get_group_members(sb_game_handle_t gs, int group_id,
sb_achievement_t *out, size_t max) {
size_t total = sb_get_cached_achievements_count(gs);
size_t found = 0;
for (size_t i = 0; i < total && found < max; i++) {
sb_achievement_t ach;
if (sb_get_cached_achievement_at(gs, i, &ach)
&& ach.group_id == group_id) {
out[found++] = ach;
}
}
return found;
}
// Find all achievements in a group
std::vector<scorbit::Achievement> getGroupMembers(
scorbit::GameState& gs,
int groupId
) {
std::vector<scorbit::Achievement> members;
auto allAchievements = gs.getAchievements();
for (const auto& ach : allAchievements) {
if (ach.groupId == groupId) {
members.push_back(ach);
}
}
return members;
}
// Usage
auto multiballGroup = getGroupMembers(gs, 1);
std::cout << "Group 1 has " << multiballGroup.size() << " achievements\n";
Example: Tiered Achievement System¶
A common pattern is tiered achievements (Bronze → Silver → Gold):
Group: "Multiball Achievements" (group_id: 5)
├── multiball_bronze - "Start 10 multiballs" (count: 10)
├── multiball_silver - "Start 50 multiballs" (count: 50)
└── multiball_gold - "Start 100 multiballs" (count: 100)
void display_tiered_progress(sb_game_handle_t gs, int64_t user_id, int group_id) {
sb_achievement_t members[32];
size_t count = get_group_members(gs, group_id, members, 32);
for (size_t i = 0; i < count; i++) {
sb_achievement_progress_t prog;
bool unlocked = sb_get_cached_progress(gs, user_id, members[i].key, &prog)
&& prog.unlocked;
int current = unlocked ? prog.progress : 0;
printf("%s %s: %d/%d\n", unlocked ? "✓" : "○",
members[i].name, current, members[i].count);
}
}
// Display tiered achievement progress
void displayTieredProgress(scorbit::GameState& gs, int64_t userId, int groupId) {
auto members = getGroupMembers(gs, groupId);
// Sort by target count for tier ordering
std::sort(members.begin(), members.end(),
[](const auto& a, const auto& b) { return a.count < b.count; });
for (const auto& ach : members) {
auto progress = gs.getProgress(userId, ach.key);
int current = progress ? progress->progress : 0;
bool unlocked = progress ? progress->unlocked : false;
std::cout << (unlocked ? "✓ " : "○ ")
<< ach.name << ": "
<< current << "/" << ach.count << "\n";
}
}
def display_tiered_progress(gs, user_id, group_id):
"""Display tiered achievement progress."""
members = get_group_members(gs, group_id)
# Sort by target count for tier ordering
members.sort(key=lambda a: a.count)
for ach in members:
progress = gs.get_progress(user_id, ach.key)
current = progress.progress if progress else 0
unlocked = progress.unlocked if progress else False
status = "✓" if unlocked else "○"
print(f"{status} {ach.name}: {current}/{ach.count}")
Displaying Grouped Achievements¶
When displaying achievements, consider grouping them visually:
void display_grouped_achievements(sb_game_handle_t gs) {
size_t total = sb_get_cached_achievements_count(gs);
// Collect unique group IDs (simple approach)
int seen_groups[64];
size_t num_groups = 0;
for (size_t i = 0; i < total; i++) {
sb_achievement_t ach;
if (!sb_get_cached_achievement_at(gs, i, &ach)) continue;
if (ach.group_id <= 0) continue;
// Check if group already seen
bool found = false;
for (size_t g = 0; g < num_groups; g++) {
if (seen_groups[g] == ach.group_id) { found = true; break; }
}
if (!found && num_groups < 64)
seen_groups[num_groups++] = ach.group_id;
}
// Display each group
for (size_t g = 0; g < num_groups; g++) {
printf("=== Group %d ===\n", seen_groups[g]);
for (size_t i = 0; i < total; i++) {
sb_achievement_t ach;
if (sb_get_cached_achievement_at(gs, i, &ach)
&& ach.group_id == seen_groups[g])
printf(" %s\n", ach.name);
}
}
// Display ungrouped
printf("=== Other Achievements ===\n");
for (size_t i = 0; i < total; i++) {
sb_achievement_t ach;
if (sb_get_cached_achievement_at(gs, i, &ach) && ach.group_id <= 0)
printf(" %s\n", ach.name);
}
}
// Organize achievements by group for display
std::map<int, std::vector<scorbit::Achievement>> byGroup;
std::vector<scorbit::Achievement> ungrouped;
for (const auto& ach : gs.getAchievements()) {
if (ach.groupId > 0) {
byGroup[ach.groupId].push_back(ach);
} else {
ungrouped.push_back(ach);
}
}
// Display grouped achievements
for (const auto& [groupId, members] : byGroup) {
std::cout << "=== Group " << groupId << " ===\n";
for (const auto& ach : members) {
std::cout << " " << ach.name << "\n";
}
}
// Display standalone achievements
std::cout << "=== Other Achievements ===\n";
for (const auto& ach : ungrouped) {
std::cout << " " << ach.name << "\n";
}
from collections import defaultdict
# Organize achievements by group for display
by_group = defaultdict(list)
ungrouped = []
for ach in gs.get_achievements():
if ach.group_id > 0:
by_group[ach.group_id].append(ach)
else:
ungrouped.append(ach)
# Display grouped achievements
for group_id, members in by_group.items():
print(f"=== Group {group_id} ===")
for ach in members:
print(f" {ach.name}")
# Display standalone achievements
print("=== Other Achievements ===")
for ach in ungrouped:
print(f" {ach.name}")
Group Progress Summary¶
Show overall progress for a group:
void display_group_summary(sb_game_handle_t gs, int64_t user_id, int group_id) {
sb_achievement_t members[32];
size_t count = get_group_members(gs, group_id, members, 32);
int unlocked = 0;
for (size_t i = 0; i < count; i++) {
sb_achievement_progress_t prog;
if (sb_get_cached_progress(gs, user_id, members[i].key, &prog)
&& prog.unlocked)
unlocked++;
}
printf("Group %d: %d/%zu complete\n", group_id, unlocked, count);
}
void displayGroupSummary(scorbit::GameState& gs, int64_t userId, int groupId) {
auto members = getGroupMembers(gs, groupId);
int totalUnlocked = 0;
int totalCount = members.size();
for (const auto& ach : members) {
auto progress = gs.getProgress(userId, ach.key);
if (progress && progress->unlocked) {
totalUnlocked++;
}
}
std::cout << "Group " << groupId << ": "
<< totalUnlocked << "/" << totalCount << " complete\n";
}
def display_group_summary(gs, user_id, group_id):
"""Show overall progress for a group."""
members = get_group_members(gs, group_id)
total_unlocked = 0
for ach in members:
progress = gs.get_progress(user_id, ach.key)
if progress and progress.unlocked:
total_unlocked += 1
print(f"Group {group_id}: {total_unlocked}/{len(members)} complete")
UI Considerations
- Show group progress (e.g., "3/5 completed")
- Collapse completed groups to reduce clutter
- Highlight the next achievable tier in a group
- Use visual indicators to show group relationships