Skip to content

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).

KeyDescription
enabledSet to false to disable the entire system. Default: true
artArt file names for the four achievement interrupt frames (see Art below)
achievementsMap of achievement tag → achievement definition

Each entry under achievements has a unique tag (e.g. user_login_count) and the following fields:

FieldRequiredDescription
typeYesTrigger type — see Achievement Types
statNameYesThe user property stat name that drives this achievement
retroactiveNoIf 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.
matchYesMap of numeric threshold → match details

Each key under match is a numeric threshold. When the triggering stat reaches or exceeds that value, the corresponding tier is awarded.

FieldRequiredDescription
titleYesShort title for the achievement tier
textYesPrivate notification text shown only to the earning user. Supports format variables.
globalTextNoText broadcast to all other online users when this tier is earned. Omit to suppress global notifications. Supports format variables.
pointsYesPoints 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
}
}
}

The type field controls when and how the stat value is compared against thresholds.

TypeFires onValue compared
userStatSetAny setUserStat callThe new absolute value of the stat
userStatIncAny incrementUserStat callThe increment delta for that single event (not the running total)
userStatIncNewValAny incrementUserStat callThe 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. Set retroactive: false for these since they’re per-event.

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.

VariableDescription
{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

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):

KeyDescription
localHeaderHeader shown above the private (local) achievement notification
localFooterFooter shown below the private notification
globalHeaderHeader shown above the global broadcast notification
globalFooterFooter 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.


The default config/achievements.hjson ships with the following achievement categories. All can be customized, extended with additional tiers, or removed entirely.

AchievementTypeStatTiers
user_login_countuserStatSetlogin_count2, 10, 25, 75, 100, 250, 500 logins
user_login_streakuserStatSetlogin_streak_days7, 14, 30, 60, 100, 365 consecutive days
user_post_countuserStatIncNewValpost_count2, 5, 20, 100, 250, 500 posts
user_mail_sent_countuserStatIncNewValmail_sent_count1, 10, 50, 200 messages
user_node_msg_sent_countuserStatIncNewValnode_msg_sent_count1, 10, 50, 100 messages
user_upload_countuserStatIncNewValul_total_count1, 10, 50, 100, 200 files
user_upload_bytesuserStatSetul_total_bytes10 KB → 23 GB
user_download_countuserStatIncNewValdl_total_count1, 10, 50, 100, 200 files
user_download_bytesuserStatSetdl_total_bytes640 KB → 5 GB
user_session_dl_countuserStatIncdl_total_count5, 15, 30, 45, 60, 90, 120 in one session
user_session_ul_countuserStatIncul_total_count5, 15, 30, 60 in one session
user_ul_dl_ratiouserStatSetul_dl_ratio0.25:1 → 10:1 ratio
user_door_runsuserStatIncNewValdoor_run_total_count1, 10, 50, 100, 200 runs
user_individual_door_run_minutesuserStatIncdoor_run_total_minutes1, 5, 15, 30, 60, 120, 240 min single session
user_door_run_total_minutesuserStatSetdoor_run_total_minutes10, 30, 60, 120, 240 min lifetime
user_account_ageuserStatSetaccount_days_old30 days, 1, 2, 5 years
user_failed_login_countuserStatIncNewValfailed_login_attempts3, 10, 25, 50 failures (0 pts — shame badges)
user_total_system_online_minutesuserStatSetminutes_online_total_count30 min → 1440 min lifetime

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.

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.


Two ACS codes check achievement progress:

CodeCondition
AC<n>User has earned >= n total achievements
AP<n>User has >= n total achievement points

See ACS for general ACS documentation.


Two MCI codes expose achievement stats for use in art/menus:

CodeDescription
%ACCurrent user’s total achievement count
%APCurrent user’s total achievement points

See MCI for general MCI documentation.


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
}
}