diff --git a/internal/plugin/achievements.go b/internal/plugin/achievements.go index 99a298f..fdd353c 100644 --- a/internal/plugin/achievements.go +++ b/internal/plugin/achievements.go @@ -1206,9 +1206,39 @@ func (p *AchievementsPlugin) buildAchievements() []achievementDef { return false }, }, + // N7/B4 — the Renown wing (gogobee_engagement_plan.md §B4). Passive checks + // against the derived Renown level (N7/B2); no event hook needed. + { + ID: "renown_1", Name: "Beyond the Cap", Description: "Earned your first level of Renown. The story was supposed to be over.", + Emoji: "✦", + Check: func(d *sql.DB, u id.UserID) bool { return renownAtLeast(d, u, 1) }, + }, + { + ID: "renown_5", Name: "Storied", Description: "Reached Renown 5. They tell versions of your runs that never happened.", + Emoji: "✦", + Check: func(d *sql.DB, u id.UserID) bool { return renownAtLeast(d, u, 5) }, + }, + { + ID: "renown_10", Name: "Fabled", Description: "Reached Renown 10. Somewhere a bard is getting rich off your name.", + Emoji: "✦", + Check: func(d *sql.DB, u id.UserID) bool { return renownAtLeast(d, u, 10) }, + }, } } +// renownAtLeast reports whether the player's derived Renown level (N7/B2) has +// reached level, reading player_meta.renown_xp through the passed handle. +func renownAtLeast(d *sql.DB, userID id.UserID, level int) bool { + var xp int + if err := d.QueryRow( + `SELECT renown_xp FROM player_meta WHERE user_id = ?`, + string(userID), + ).Scan(&xp); err != nil { + return false + } + return renownLevelFor(xp) >= level +} + // ── Expedition achievement helpers ────────────────────────────────────────── // clearedZoneIDs returns the zones this player has beaten outright. Boss- diff --git a/internal/plugin/adventure_renown_test.go b/internal/plugin/adventure_renown_test.go index bc9f05a..3071efb 100644 --- a/internal/plugin/adventure_renown_test.go +++ b/internal/plugin/adventure_renown_test.go @@ -147,6 +147,32 @@ func TestAddRenownXP_Accumulates(t *testing.T) { } } +// TestRenownAtLeast — the B4 achievement gate reads the derived level correctly. +func TestRenownAtLeast(t *testing.T) { + newRenownTestDB(t) + uid := id.UserID("@renown_ach:example") + if err := createAdvCharacter(uid, "Achiever"); err != nil { + t.Fatal(err) + } + d := db.Get() + if renownAtLeast(d, uid, 1) { + t.Errorf("no renown yet, should not meet level 1") + } + if _, _, err := addRenownXP(uid, renownXPPerLevel*5); err != nil { + t.Fatal(err) + } + if !renownAtLeast(d, uid, 5) { + t.Errorf("renown 5 should meet level 5") + } + if renownAtLeast(d, uid, 6) { + t.Errorf("renown 5 should not meet level 6") + } + // Absent player → false, not a crash. + if renownAtLeast(d, id.UserID("@nobody:nowhere.invalid"), 1) { + t.Errorf("absent player should not meet any renown level") + } +} + // TestRenownOverlay — applyPlayerMetaOverlay populates RenownXP and RenownLevel. func TestRenownOverlay(t *testing.T) { newRenownTestDB(t)