18 data-driven achievements (achievements.json) across 8 trigger categories: matches_played, matches_won, goals_scored, packs_opened, sbcs_completed, cards_owned, level, objectives_completed, drafts_completed. Rarities span common → epic. Coin rewards range from 500 (first_match) to 6000 (win_50). check_and_unlock() queries the relevant metric from existing tables, skips already-earned achievements via INSERT OR IGNORE, grants coin rewards, and fires a persistent notification per unlock. Trigger values are cached per call to avoid redundant DB round-trips for same-trigger achievements. Checks run automatically after every match result (all triggers), every pack open (packs_opened), and every successful SBC submission (sbcs_completed). GET /achievements returns all definitions annotated with unlocked/unlocked_at, plus earned and total counts. POST /matches/result response gains an achievements_unlocked array (empty when nothing new unlocked). AppState gains achievement_defs (Arc<Vec<AchievementDefinition>>) loaded from data/achievements/**/*.json at startup — same pattern as obj_defs. 5 new tests: list endpoint, first_match unlock, first_win unlock, no-dup guard, coin reward verification. Core now at 82 tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
[
|
||||
{
|
||||
"id": "first_match",
|
||||
"title": "Debut Match",
|
||||
"description": "Play your very first match.",
|
||||
"icon": "⚽",
|
||||
"trigger": "matches_played",
|
||||
"threshold": 1,
|
||||
"reward_coins": 500,
|
||||
"rarity": "common"
|
||||
},
|
||||
{
|
||||
"id": "match_veteran",
|
||||
"title": "Match Veteran",
|
||||
"description": "Play 10 matches.",
|
||||
"icon": "🏟️",
|
||||
"trigger": "matches_played",
|
||||
"threshold": 10,
|
||||
"reward_coins": 1500,
|
||||
"rarity": "common"
|
||||
},
|
||||
{
|
||||
"id": "match_pro",
|
||||
"title": "Pro Player",
|
||||
"description": "Play 50 matches.",
|
||||
"icon": "🎖️",
|
||||
"trigger": "matches_played",
|
||||
"threshold": 50,
|
||||
"reward_coins": 4000,
|
||||
"rarity": "rare"
|
||||
},
|
||||
{
|
||||
"id": "first_win",
|
||||
"title": "First Blood",
|
||||
"description": "Win your first match.",
|
||||
"icon": "🏆",
|
||||
"trigger": "matches_won",
|
||||
"threshold": 1,
|
||||
"reward_coins": 500,
|
||||
"rarity": "common"
|
||||
},
|
||||
{
|
||||
"id": "win_10",
|
||||
"title": "On Fire",
|
||||
"description": "Win 10 matches.",
|
||||
"icon": "🔥",
|
||||
"trigger": "matches_won",
|
||||
"threshold": 10,
|
||||
"reward_coins": 2000,
|
||||
"rarity": "rare"
|
||||
},
|
||||
{
|
||||
"id": "win_50",
|
||||
"title": "Unstoppable",
|
||||
"description": "Win 50 matches.",
|
||||
"icon": "👑",
|
||||
"trigger": "matches_won",
|
||||
"threshold": 50,
|
||||
"reward_coins": 6000,
|
||||
"rarity": "epic"
|
||||
},
|
||||
{
|
||||
"id": "goalscorer_10",
|
||||
"title": "Goalscorer",
|
||||
"description": "Score 10 goals across all matches.",
|
||||
"icon": "⚡",
|
||||
"trigger": "goals_scored",
|
||||
"threshold": 10,
|
||||
"reward_coins": 750,
|
||||
"rarity": "common"
|
||||
},
|
||||
{
|
||||
"id": "top_scorer_50",
|
||||
"title": "Top Scorer",
|
||||
"description": "Score 50 goals across all matches.",
|
||||
"icon": "🥅",
|
||||
"trigger": "goals_scored",
|
||||
"threshold": 50,
|
||||
"reward_coins": 3000,
|
||||
"rarity": "rare"
|
||||
},
|
||||
{
|
||||
"id": "first_pack",
|
||||
"title": "Pack Opener",
|
||||
"description": "Open your first pack.",
|
||||
"icon": "📦",
|
||||
"trigger": "packs_opened",
|
||||
"threshold": 1,
|
||||
"reward_coins": 500,
|
||||
"rarity": "common"
|
||||
},
|
||||
{
|
||||
"id": "pack_10",
|
||||
"title": "Pack Addict",
|
||||
"description": "Open 10 packs.",
|
||||
"icon": "🎁",
|
||||
"trigger": "packs_opened",
|
||||
"threshold": 10,
|
||||
"reward_coins": 2500,
|
||||
"rarity": "rare"
|
||||
},
|
||||
{
|
||||
"id": "collector_20",
|
||||
"title": "Collector",
|
||||
"description": "Own 20 cards in your collection.",
|
||||
"icon": "🗂️",
|
||||
"trigger": "cards_owned",
|
||||
"threshold": 20,
|
||||
"reward_coins": 1000,
|
||||
"rarity": "common"
|
||||
},
|
||||
{
|
||||
"id": "hoarder_50",
|
||||
"title": "Hoarder",
|
||||
"description": "Own 50 cards in your collection.",
|
||||
"icon": "💎",
|
||||
"trigger": "cards_owned",
|
||||
"threshold": 50,
|
||||
"reward_coins": 3500,
|
||||
"rarity": "rare"
|
||||
},
|
||||
{
|
||||
"id": "first_sbc",
|
||||
"title": "Squad Builder",
|
||||
"description": "Complete your first SBC challenge.",
|
||||
"icon": "🧩",
|
||||
"trigger": "sbcs_completed",
|
||||
"threshold": 1,
|
||||
"reward_coins": 750,
|
||||
"rarity": "common"
|
||||
},
|
||||
{
|
||||
"id": "sbc_master",
|
||||
"title": "SBC Master",
|
||||
"description": "Complete 5 SBC challenges.",
|
||||
"icon": "🔧",
|
||||
"trigger": "sbcs_completed",
|
||||
"threshold": 5,
|
||||
"reward_coins": 4000,
|
||||
"rarity": "epic"
|
||||
},
|
||||
{
|
||||
"id": "level_5",
|
||||
"title": "Rising Star",
|
||||
"description": "Reach Level 5.",
|
||||
"icon": "⬆",
|
||||
"trigger": "level",
|
||||
"threshold": 5,
|
||||
"reward_coins": 2000,
|
||||
"rarity": "rare"
|
||||
},
|
||||
{
|
||||
"id": "level_10",
|
||||
"title": "Elite Player",
|
||||
"description": "Reach Level 10.",
|
||||
"icon": "⭐",
|
||||
"trigger": "level",
|
||||
"threshold": 10,
|
||||
"reward_coins": 5000,
|
||||
"rarity": "epic"
|
||||
},
|
||||
{
|
||||
"id": "first_objective",
|
||||
"title": "On a Mission",
|
||||
"description": "Complete your first objective.",
|
||||
"icon": "✅",
|
||||
"trigger": "objectives_completed",
|
||||
"threshold": 1,
|
||||
"reward_coins": 500,
|
||||
"rarity": "common"
|
||||
},
|
||||
{
|
||||
"id": "draft_debut",
|
||||
"title": "Draft Veteran",
|
||||
"description": "Complete a draft session.",
|
||||
"icon": "🃏",
|
||||
"trigger": "drafts_completed",
|
||||
"threshold": 1,
|
||||
"reward_coins": 1500,
|
||||
"rarity": "rare"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user