add-achievement-data
Transform raw achievement lists (IDs + comments) into properly formatted Lua achievement data entries for Krowi's Achievement Filter addon. Handles reward type mapping, season references, faction detection, and maintains consistent code style.
Install
mkdir -p .claude/skills/add-achievement-data && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16614" && unzip -o skill.zip -d .claude/skills/add-achievement-data && rm skill.zipInstalls to .claude/skills/add-achievement-data
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Transform raw achievement lists (IDs + comments) into properly formatted Lua achievement data entries for Krowi's Achievement Filter addon. Handles reward type mapping, season references, faction detection, and maintains consistent code style.About this skill
Transform raw achievement lists (IDs + comments) into properly formatted Lua achievement data entries for the Krowi's Achievement Filter addon. Uses the V2 fluent builder format (Ach(id):Method():...) which is the current standard for all new data across all expansions and clients.
Provide your achievement list in this format and I'll:
- Scan the currently open AchievementData.lua file to detect the expansion and latest patch
- Confirm the target patch with you (append to existing or create new)
- Query wow.tools.local for each achievement to get authoritative name, faction, and reward data
- Cross-check Wowhead for any reward or description details the DB doesn't clarify
- Parse achievements, detect rewards, seasons, and factions — using DB + web data to fill gaps
- Generate properly formatted Lua code using V2 format
- Insert or display the results
When to Use
- Adding new batch of achievements for a patch
- Converting achievement data from WoW API or external sources
- Bulk adding achievements with their associated metadata
Input Format
Provide a raw achievement list with this format:
{AchievementID}, -- Achievement Name (optional: Reward: description, Season/Event info, Faction)
Example:
{61792}, -- T-A-G that spells "Gotcha!"
{61793}, -- Deployed to the Void
{ -- Abyss Anglers: Pressurized Eyeglass (Reward: Unlock Pressurized Eyeglass for purchase from Depthdiver Jeju)
62506,
{
RewardType = rewardType.NotCategorized,
},
},
Processing Steps
0. Determine Target Patch (First Step)
Before parsing achievements, use the Patch Version Determination process (see section below) to establish which patch/expansion these achievements will be added to.
0.5. DB & Web Lookup (Before Parsing)
Before applying comment-based heuristics, query authoritative sources. The DB is ground truth; Wowhead fills gaps.
wow.tools.local (primary — game DB)
Query all IDs in a single batch. Use the build that matches the expansion/client being worked on. Full API reference: .github/skills/verify-achievement-data/API.md.
# Batch lookup — replace with actual IDs and build
$ids = @(61792, 61793, 62506)
$build = "12.0.5.67602" # retail; use wow_classic build string for Classic files
$pat = "^(" + ($ids -join "|") + ")$"
$body = "draw=1&start=0&length=$($ids.Count + 10)&columns[3][search][value]=$pat&columns[3][search][regex]=true"
$resp = Invoke-WebRequest "http://localhost:5000/dbc/data/achievement/?build=$build" `
-Method POST -Body $body -ContentType "application/x-www-form-urlencoded" -UseBasicParsing
$rows = ($resp.Content | ConvertFrom-Json).data
$byId = @{}
$rows | ForEach-Object { $byId[$_[3]] = $_ }
Column index map (key fields for data entry):
| Index | DB column | What it tells you |
|---|---|---|
| 0 | Description_lang | Achievement description — use when reward type or category is ambiguous |
| 1 | Title_lang | Authoritative achievement name — use to correct inline comments |
| 2 | Reward_lang | Non-empty means reward exists. "Title Reward: X" or "Title: X" → :Title(). "Reward: X" (no "Title") → tangible reward (mount/pet/toy/etc.) |
| 3 | ID | Confirm ID exists in DB (recordsFiltered == 0 means not found) |
| 5 | Faction | -1 = both, 0 = Horde only, 1 = Alliance only |
| 13 | RewardItemID | Non-"0" = item reward exists (Retail only — unreliable in Classic) |
What to do with DB data:
Faction == 0or1→ apply:FactionSplit()or:AutoFactionSplit()as appropriate; look for counterpart ID (typically ±1)Reward_langstarts with"Title"→ add:Title()Reward_langnon-empty without"Title"prefix → lookup the specific reward type on WowheadrecordsFiltered == 0→ ID not in this build; try alternate build or ask user to confirm IDTitle_langdiffers from inline comment → correct the comment to match DB name
Build selection: Retail files → wow build (e.g. 12.0.5.67602). Classic files → wow_classic (e.g. 5.5.3.67509 for Cata/MoP Classic) or wow_classic_era. Probe the DB with a known ID to confirm. See API.md for full build table.
Wowhead (secondary — reward and description detail)
Use Wowhead when Reward_lang is ambiguous, empty but a reward is suspected, or you need to see the exact reward item/mount/pet name.
| Client | URL pattern |
|---|---|
| Retail | https://www.wowhead.com/achievement=ID |
| Cata / MoP Classic | https://www.wowhead.com/classic/achievement=ID |
| WotLK Classic | https://www.wowhead.com/wotlk/achievement=ID |
On the Wowhead page, look for:
| Wowhead section | Maps to |
|---|---|
| Title Reward | :Title() |
| Mount in rewards | :Mount() |
| Battle Pet in rewards | :Pet() |
| Toy in rewards | :Toy() |
| Tabard in rewards | :Tabard() |
| Appearance / Transmog set in rewards | :Transmog() |
| Unlocks for purchase (vendor item, not directly awarded) | :NotCategorized() |
| Unlock travel method | :Mount() |
| Housing decoration | :HousingDecor() |
| Warband Campsite | :WarbandCampsite() |
| No reward section | Simple Ach(id) |
Tip: Wowhead also shows faction restrictions, category (PvP / Dungeon & Raids / etc.), and "unavailable" banners — use these to confirm
:IsPvP(),:FactionSplit(), and:Obtainable()needs.
1. Parse Achievement ID
Extract numeric ID from each entry. IDs must be valid positive integers.
2. Extract Comment
Capture the comment after --. Comments contain:
- Achievement name (required)
- Reward type hint in parentheses (optional)
- Season/Event references (optional)
- Faction indicator (optional)
3. Reward Type Detection
Map comment text to AchBuilder method names:
| Keyword in Comment | Method |
|---|---|
| "Mount:", "Mount: " | :Mount() |
| "Title:", "Title: " | :Title() |
| "Pet:", "Pet: " | :Pet() |
| "Toy:" | :Toy() |
| "Tabard:" | :Tabard() |
| "Transmog:" | :Transmog() |
| "Unlock ..., for purchase" | :NotCategorized() |
| "Unlock ... travel method" | :Mount() |
| "Unlock ... decor" | :HousingDecor() |
| "Character Unlock:", "Character Title:", "Seasonal Character Title:" | :Title() |
| "Shop Sign" | :HousingDecor() |
| Bronze Cache | :RemixBronze() |
| Allied Race unlock | :AlliedRace() |
| Warband Campsite | :WarbandCampsite() |
| (no clear match) | Ask for clarification |
Reward type methods (all 17):
:NotCategorized() :Other() :AlliedRace() :Garrison() :Mount() :Pet() :RemixBronze() :Tabard() :Teleport() :Title() :Toy() :TradersTender() :Transmog() :WarbandCampsite() :RemixInfiniteKnowledge() :HousingDecor() :KeystoneResilience()
Additional (non-reward) methods:
:IsPvP() :PvP(N) :PvE(N) :IsRealmFirst() :Obtainable(...) :Anniv20() :FactionSplit(faction, altId) :AutoFactionSplit(faction, altId)
Rules:
- If multiple reward types apply (e.g., "Title and Mount"), chain both:
Ach(id):Title():Mount() - If comment contains "Reward:" but no recognizable keyword, ask user for clarification
- If no reward hint in comment, use simple form:
Ach(id), -- Comment
4. Season/Event Reference Detection
Extract from comment text:
| Pattern | Action |
|---|---|
| "Season X" (X = number) | Detect context (PvP or PvE) from surrounding text |
| "PvE Season", "Keystone", "Raid", "Dungeon" | Use :PvE(X) where X is the game season number (e.g., 13 for TWW S1) |
| "PvP Season", "Gladiator", "Duelist", "Rival", "Combatant", "Challenger" | Use :PvP(X) where X is the arena season number |
| PvP context but no season number (e.g., "Battleground", "Tour of Duty") | Use :IsPvP() (sets PvP flag only, no season reference) |
| "Realm First!" in name | Use :IsRealmFirst():Obtainable("Once") |
| Event name (e.g., "Remix Event", "Anniversary") | Try to find matching event ID in codebase or ask user |
:IsPvP() vs :PvP(N):
:PvP(N)— Achievement is PvP AND tied to a specific season (e.g., gladiator titles, seasonal mounts):IsPvP()— Achievement is PvP but NOT tied to a specific season (e.g., battleground wins, tour of duty, conquest accumulation)- Both can be combined with reward type methods:
Ach(id):Pet():IsPvP()
5. Faction Detection
Scan comment for faction keywords:
| Text | Action |
|---|---|
| Contains "Alliance" | Use :AutoFactionSplit(faction.Alliance, HORDE_ID) |
| Contains "Horde" | Use :AutoFactionSplit(faction.Horde, ALLIANCE_ID) |
| Faction-specific with no counterpart | Use :FactionSplit(faction.X, nil) |
| No faction keyword | Default to faction-neutral (no FactionSplit) |
:FactionSplit(faction.X, nil) — faction-only, no paired achievement:
Use when an achievement exists only for one faction with no counterpart (e.g., some Allied Race unlocks):
Ach(12242):FactionSplit(faction.Alliance, nil):AlliedRace(),
Faction Pairing:
If detecting either faction variant, automatically look for the counterpart (typically ID+1 or ID-1). Collapse both into a single entry on the lower-ID achievement. Comment order follows the entry: lower-ID name first, higher-ID name second, separated by /:
-- Same name on both sides:
Ach(LOWER_ID):AutoFactionSplit(faction.X, HIGHER_ID), -- Achievement Name
-- Different names:
Ach(LOWER_ID):AutoFactionSplit(faction.X, HIGHER_ID), -- Lower-ID Name / Higher-ID Name
-- Shared prefix and/or suffix — merge them, keep only the unique parts in the middle:
Ach(40234):AutoFactionSplit(faction.Horde, 40235):Title():PvP(38), -- Forged Warlord / Marshal: The War Within Season 1
Use :FactionSplit() (two explicit lines) only when the two achievements have different reward types (e.g., one faction gets a reward the other doesn't).
6. Generate Lua Entry
Build the Ach() call based on
Content truncated.