Achievements
Achievements
ENiGMA½ includes a built-in achievement system that rewards users for activity on the board. Achievements are defined in config/achievements.hjson and fire automatically as users accumulate stats. When an achievement is earned the user sees a private interrupt notification; a separate global notification is broadcast to all other online users (if globalText is defined for that tier).
Configuration
Top-level keys
| Key | Description |
|---|---|
enabled |
Set to false to disable the entire system. Default: true |
art |
Art file names for the four achievement interrupt frames (see Art below) |
achievements |
Map of achievement tag → achievement definition |
Achievement Definition
Each entry under achievements has a unique tag (e.g. user_login_count) and the following fields:
| Field | Required | Description |
|---|---|---|
type |
Yes | Trigger type — see Achievement Types |
statName |
Yes | The user property stat name that drives this achievement |
retroactive |
No | If true (default), earning a higher tier also awards all lower unarned tiers in the same achievement. Set to false to only award the exact tier reached. |
match |
Yes | Map of numeric threshold → match details |
Match Details
Each key under match is a numeric threshold. When the triggering stat reaches or exceeds that value, the corresponding tier is awarded.
| Field | Required | Description |
|---|---|---|
title |
Yes | Short title for the achievement tier |
text |
Yes | Private notification text shown only to the earning user. Supports format variables. |
globalText |
No | Text broadcast to all other online users when this tier is earned. Omit to suppress global notifications. Supports format variables. |
points |
Yes | Points awarded. Set to 0 for shame/novelty badges that carry no score. |
Example:
user_login_count: {
type: userStatSet
statName: login_count
match: {
10: {
title: "Curious Caller"
globalText: "{userName} has logged into {boardName} {achievedValue} times!"
text: "You've logged into {boardName} {achievedValue} times!"
points: 10
}
25: {
title: "Inquisitive"
text: "You've logged into {boardName} {achievedValue} times!"
points: 15
}
}
}
Achievement Types
The type field controls when and how the stat value is compared against thresholds.
| Type | Fires on | Value compared |
|---|---|---|
userStatSet |
Any setUserStat call |
The new absolute value of the stat |
userStatInc |
Any incrementUserStat call |
The increment delta for that single event (not the running total) |
userStatIncNewVal |
Any incrementUserStat call |
The new cumulative total after the increment |
When to use each:
userStatSet— Use when the stat is written as an absolute value (login count, streak days, account age in days, UL/DL ratio). The system compares the stored value against your thresholds.userStatIncNewVal— Use when the stat is incremented and you want to award based on lifetime totals (total messages posted, total mail sent, total files downloaded).userStatInc— Use when you want to award based on a single-event quantity (e.g. “downloaded 30 files in one session”). The threshold is compared against the per-event delta, not the running total. Setretroactive: falsefor these since they’re per-event.
Format Variables
The following variables may be used in text, globalText, and title fields via {variableName} syntax. ENiGMA½ string format modifiers (e.g. {achievedValue!sizeWithAbbr}) are supported.
| Variable | Description |
|---|---|
{userName} |
Username of the user who earned the achievement |
{userRealName} |
User’s real name |
{userLocation} |
User’s location |
{userAffils} |
User’s affiliations |
{nodeId} |
Node the user is on |
{title} |
Title of the earned tier |
{points} |
Points awarded for this tier |
{achievedValue} |
The threshold value that was met (e.g. 25 for the 25-login tier) |
{boardName} |
System board name from general.boardName in config |
{timestamp} |
ISO timestamp when the achievement was earned |
Art
Four art files frame achievement interrupt displays. These are configured under the top-level art key and reference art files by name (resolved from the active theme, then system defaults):
| Key | Description |
|---|---|
localHeader |
Header shown above the private (local) achievement notification |
localFooter |
Footer shown below the private notification |
globalHeader |
Header shown above the global broadcast notification |
globalFooter |
Footer shown below the global broadcast notification |
Art files support the same format variables as text fields, so you can display the achievement title, points, and user name directly in the art.
Built-in Achievements
The default config/achievements.hjson ships with the following achievement categories. All can be customized, extended with additional tiers, or removed entirely.
| Achievement | Type | Stat | Tiers |
|---|---|---|---|
user_login_count |
userStatSet |
login_count |
2, 10, 25, 75, 100, 250, 500 logins |
user_login_streak |
userStatSet |
login_streak_days |
7, 14, 30, 60, 100, 365 consecutive days |
user_post_count |
userStatIncNewVal |
post_count |
2, 5, 20, 100, 250, 500 posts |
user_mail_sent_count |
userStatIncNewVal |
mail_sent_count |
1, 10, 50, 200 messages |
user_node_msg_sent_count |
userStatIncNewVal |
node_msg_sent_count |
1, 10, 50, 100 messages |
user_upload_count |
userStatIncNewVal |
ul_total_count |
1, 10, 50, 100, 200 files |
user_upload_bytes |
userStatSet |
ul_total_bytes |
10 KB → 23 GB |
user_download_count |
userStatIncNewVal |
dl_total_count |
1, 10, 50, 100, 200 files |
user_download_bytes |
userStatSet |
dl_total_bytes |
640 KB → 5 GB |
user_session_dl_count |
userStatInc |
dl_total_count |
5, 15, 30, 45, 60, 90, 120 in one session |
user_session_ul_count |
userStatInc |
ul_total_count |
5, 15, 30, 60 in one session |
user_ul_dl_ratio |
userStatSet |
ul_dl_ratio |
0.25:1 → 10:1 ratio |
user_door_runs |
userStatIncNewVal |
door_run_total_count |
1, 10, 50, 100, 200 runs |
user_individual_door_run_minutes |
userStatInc |
door_run_total_minutes |
1, 5, 15, 30, 60, 120, 240 min single session |
user_door_run_total_minutes |
userStatSet |
door_run_total_minutes |
10, 30, 60, 120, 240 min lifetime |
user_account_age |
userStatSet |
account_days_old |
30 days, 1, 2, 5 years |
user_failed_login_count |
userStatIncNewVal |
failed_login_attempts |
3, 10, 25, 50 failures (0 pts — shame badges) |
user_total_system_online_minutes |
userStatSet |
minutes_online_total_count |
30 min → 1440 min lifetime |
Login Streak Notes
The login streak system uses an anti-cheat minimum gap of 20 hours between logins before a new login counts toward the streak. This prevents the “midnight exploit” where logging in at 11:58 PM and again at 12:02 AM would count as two separate days despite only 4 minutes apart. A 48-hour grace window means missing a day by a few hours doesn’t break the streak — useful for users who log in at slightly different times each day.
UL/DL Ratio Notes
The ul_dl_ratio stat is stored as an integer representing (uploads / downloads) * 100. A value of 100 means a 1:1 ratio; 200 means 2:1; 50 means 0.5:1. The ratio is only computed once the user has at least one upload and one download on record — new accounts are excluded to avoid divide-by-zero.
ACS Integration
Two ACS codes check achievement progress:
| Code | Condition |
|---|---|
AC<n> |
User has earned >= n total achievements |
AP<n> |
User has >= n total achievement points |
See ACS for general ACS documentation.
MCI Codes
Two MCI codes expose achievement stats for use in art/menus:
| Code | Description |
|---|---|
%AC |
Current user’s total achievement count |
%AP |
Current user’s total achievement points |
See MCI for general MCI documentation.
Leaderboards
Achievement totals can be surfaced in Top-X leaderboards using the User Event Log:
mciMap: {
1: {
type: userEventLog
value: achievement_pts_earned
sum: true
daysBack: 7 // omit for all-time
}
2: {
type: userEventLog
value: achievement_earned
sum: false // count individual achievements earned
daysBack: 30
}
}
