For the complete documentation index, see llms.txt. This page is also available as Markdown.

Website: Achievements

Step-by-step guide for web developers - read a character's achievement progress from the AchievementProgress table and show cards, points and an achievement-points ladder on the website, with full Nod

The game stores each character's achievement progress as one binary row. This guide shows how to read it and show it on the website: a character's achievement page (cards, tiers, progress, points) and a server-wide points ladder.

For what achievements are and how to configure them, see Achievements.

What you can show

Page
Shows

Character profile

Every achievement as a card: name, text, icon, tier, progress bar, claimed / ready / in progress. Points total in the header

Ladder

Characters sorted by achievement points

"Recently completed"

Not possible: completion times are not stored anywhere

Where the data lives

Data
Where
What you get

Progress

SQL table AchievementProgress, column Blob

One counter and one "claimed" byte per achievement

Names, texts, tiers, points

Data/Plugins/Achievements/*.xml

Everything you print

Icons

Client files Data/Interface/UI/atlas/achievements.png + achievements.xml

48 × 48 icons

CREATE TABLE AchievementProgress (
    Name varchar(10)    PRIMARY KEY,   -- character name
    Blob varbinary(1254) NOT NULL
)

No row = the character has no progress yet.

If the website runs on another machine, copy the Achievements folder to it (or export it to JSON) every time you change it on the server.

Step 1 - Read the blob

All numbers are little-endian (least significant byte first).

Offset
Size
Field

0

1

Version. Must be 1. 0 = empty row, no progress

1

1

Reserved

2

2

Count = number of entries (250)

4

5 × Count

One entry per achievement Id, in Id order (entry 0 = Id 0, entry 1 = Id 1, ...)

Each entry (5 bytes), at offset 4 + Id × 5:

Offset
Size
Field

0

4

Counter, signed 32-bit. Below 0 = treat as 0

4

1

Claimed, bit mask. Bit 0 = tier 1 claimed, bit 1 = tier 2, ... bit 7 = tier 8

Example: Id 14 is at offset 4 + 14 × 5 = 74. Bytes 58 02 00 00 01 → Counter 600, tier 1 claimed.

Step 2 - Load the achievement files

Load the folder the same way the GameServer does:

  1. Every *.xml in Data/Plugins/Achievements/, in alphabetical order.

  2. Skip Settings.xml and every file whose name starts with _.

  3. Each file has one <Category Id Name Order Icon> root.

  4. Each <Achievement Id Name Icon Text Enabled> inside it. Skip Enabled="0".

  5. Its <Tier Target Points> children, in file order. A tier whose Target is not bigger than the one before is skipped (the server skips it too). Max 8 tiers.

Keep the first achievement with a given Id; the server skips duplicates.

Parse once and cache. Reload when a file changes.

Step 3 - Work out each card

For one achievement with tiers T[0..n-1], the character's Counter and Claimed:

Value
Rule

Tier i reached

Counter >= T[i].Target

Tier i claimed

Claimed & (1 << i) is not 0

Next target

Target of the first tier not reached. None = all reached

Progress bar

Counter / next target (full when all reached)

Points earned

Sum of Points of the claimed tiers

Card state:

State
When
Suggested look

Complete

Every tier claimed

Gold / done badge

Ready to claim

A tier is reached but not claimed

Highlight. Players claim in game (Window Menu U → Achievements)

In progress

Counter > 0, nothing to claim

Normal

Not started

Counter = 0

Dimmed

Tier completion is worked out from Counter, it is not stored. If the server owner raises a Target, an unclaimed tier can go back to "not reached". Claimed tiers never change.

The character's achievement points = sum of "points earned" over every achievement. That is the number the game shows in the window header. Max points = sum of every tier's Points.

Counters that keep the highest value: ReachLevel, Reset and ItemUpgrade achievements store the best value seen (level 350, 12 resets, +13), not a count. Show them as 350 / 400, the same as the others. PlayTime counts minutes.

Step 4 - Icons

The game icons are in one image (achievements.png) with a map of where each icon is (achievements.xml):

Copy achievements.png to the website and use CSS sprites. An achievement's Icon="ach/bloodcastle" → background-position: -50px 0. Missing icon → use ach/default, then an empty box (the game does the same).

Full code

Uses mssql and fast-xml-parser (npm i fast-xml-parser).

Uses PDO (sqlsrv) and SimpleXML.

Rendering a card (React example):

Group the cards by card.def.category and show the categories as tabs, in order, like the game.

Worked example

02 - Progression.xml has (rewards left out):

Blob bytes 74-78 (Id 14): 58 02 00 00 01.

Counter

0x00000258 = 600

Claimed

0x01 = tier 1 claimed

Reached

tiers 1 and 2 (600 ≥ 60, 600 ≥ 600)

Next target

6000 → bar at 10%

Points earned

5 (only tier 1 claimed)

State

Ready to claim (tier 2 reached, not claimed)

Important Behavior

  • Data delay. Counters are saved every AutosaveSec seconds (default 180) and on logout. Tier completions and claims save at once. So an online player's progress bars can be up to 3 minutes behind; claimed tiers and points are always current.

  • Rows by character name. A character created after the plugin was enabled has no row until it first logs in.

  • Deleted characters keep their row. Join with Character (as the ladder code does) so they do not show up.

  • Plugin off or unlicensed. The rows stay, the game shows nothing. The website cannot see the license; if the paid module is not licensed, do not show the pages.

  • Removed achievement. Its progress stays in the blob but there is no definition, so it is not shown. Never reuse an Id for a different achievement.

  • Rewards are not needed on the website. Players claim in game; the site only shows status.

Common Issues

Last updated