To write your first configuration file you will first want to analyze how this framework handles input and output of data.
The framework will read all the configurations present in [code single]Data/SKSE/Plugins/AchievementsData[/code] and will inject the achievements present in the configuration files via the event listeners that are available. Each configuration file refers to a specific plugin, but more on that later.
When an achievement is unlocked, this will be saved in [code single]SKSE/Plugins/AchievementInjector[/code], and by default achievements are character-based, which means that each character will have its own savefile. It's possible to enable global achievements through the mod configuration menu. These files will contain the data regarding achievements that have been completed or that are ongoing, they will not contain achievements that haven't been started.
Folder StructureSKSE/Plugins/
├── AchievementsData/
| ├── Localizations/
| | ├── SkyrimSE_EN.txt
| | ├── SkyrimSE_IT.txt
| | └── ...
| ├── Icons/
| | ├── SkyrimSE.dds
| | ├── Dawnguard.dds
| | └── ...
| ├── SkyrimSE.json
| ├── ...
| └── yourmod.json
└── SKSE/
└── Plugins/
└── AchievementInjector/
└── UnlockedAchievements_YOURCHARACTER.json
JSON file schema
In this section we will analyze the structure of the achievement files that you can implement for your own mods. For reference, we will mostly use as example the Skyrim.json included with the main archive.{
"groupName": "String", // This property is the display name of the group of achievements in the achievement menu
"plugin": "String" // This property is the name of the plugin that the forms will refer to, including the extension of the plugin (Eg. "Skyrim.esm"),
"hideAchievements": "Bool" // (Optional) Hides the achievement group from the menu, defaults to true.
"achievements": [ // This is the list of achievements for your plugin
{
"achievementName": "String", // This is the display name of the achievement,
"description": "String", // This is the description of the achievement,
"joinType": "Enum", // (Optional) This two-state property defines whether you need all conditions to be met ("AND") or some conditions to be met ("OR"). Defaults to "OR".
"showPopup": "Bool", // (Optional) Whether to show the popup on achievement unlock. Defaults to True.
"notificationSound": "String", // Editor ID of the sound you want to trigger. Defaults to whatever the user has selected in the MCM.
"conditions": [ // List of conditions that will be listened to for the achievement to trigger
{
"type": "Enum", // This defines what type of listener you want to use for this condition
...args,
},
...
]
}
]
}
List of conditions and arguments
At the moment of writing this - Aug 10th 2024 - these are the supported conditions and their relative arguments. Keep in mind that all formids are RELATIVE FormIDs, which means that they are made of only six hex digits, and all formids are baseid UNLESS SPECIFIED OTHERWISE.
Note about backwards discovery: Not all condition types support checking whether that event has already happened or not. In the list, I specify which do.{
"type": "QuestStageDone", // Triggers when the character completes a specific stage of a quest. Supports backwards discovery for GT.
"formID": "String", // FormID of the quest
"stage": "Integer", // Stage that you want the trigger to be in
"operation": "Enum" // This two state property ("=="|">=") defines whether you want the trigger to be exactly at the specific stage or for all stages greater or equal to the one specified. Defaults to >=.
},
{
"type": "QuestObjectiveDone", // Triggers when the stage of the quest changes and compares the objective to the requested one. Doesn't support backwards discovery.
"formID": "String", // FormID of the quest
"objective": "Integer" // Index of the objective that should trigger
},
{
"type": "PlayerLevel", // Triggers when the player reaches a certain level. Supports backwards discovery.
"level": "Integer" // Level that you want your condition to trigger at
},
{
"type": "SkillLevel", // This triggers when the character reaches a certain level in a specific skill. Supports backwards discovery.
"skill": "String", // Editor FormID of the skill you want to listen for
"level": "Integer" // Level of the skill
},
{
"type": "ItemCrafted", // Triggers when the player crafts an item with a specific formid. This includes cooking, alchemy and smithing.
"formID": "String" // FormID of the item to craft.
},
{
"type": "LocationDiscovery", // Triggers when the player finds a location. Supports backwards discovery.
"locationID": "String", // Display name of the location that you want the player to find. (eg. "The Serpent Stone") Localization suggested but not required.
"worldspaceID": "String", // Name of the worldspace the location is in (eg. Tamriel)
},
{
"type": "LocationCleared", // Triggers when a specific location gets cleared
"formID": "String" // FormID of the location you want to clear
},
{
"type": "ItemInInventory", // This triggers when the player has a specific quantity of a specific item in its inventory.
"formID": "String", // FormID of the item you want to refer to (eg. "00000F" for Gold)
"quantity": "Integer" // Quantity of the specific item that you want the player to have
},
{
"type": "PlayerActivation", // Triggers when an actor with a specific reference FormID is activated by the player. Notice that static objects are also actors.
"formID": "String" // REFERENCE Form ID of the activator
},
{
"type": "DragonSoulAbsorbed", // Triggers when the character absorbs a specific quantity of dragon souls. Supports backwards discovery.
"quantity": "Integer" // Quantity of dragon souls to absorb
},
{
"type": "FirstEnterCell", // Triggers when the player first enters a cell (does not reset when the player leaves the cell)
"cellID": "String" // FormID of the cell
},
{
"type": "BookRead", // Triggers when the player reads a specific book or note.
"bookID": "String" // Name of the book you want the player to read. Localization suggested but not required.
},
{
"type": "ActorDeath", // Triggers when the actor with the REFR formID is killed (not necessarily by the player). Supports backwards discovery.
"formID": "String" // REFERENCE FormID of the actor
},
{
"type": "BaseActorDeath", // Triggers when you kill a specific quantity of an enemy with the specified formid or name.
"name": "String", // Name of the entity (eg. Bandit) Localization suggested but not required.
"formID": "String", // (Optional) If you want to capture a specific instance of enemy using form id, use this. This will override the name.
"quantity": "Integer" // How many enemies of this tipe you need to kill
},
{
"type": "SpellLearned", // Triggers when the player learns a specific spell. Notice that magic powers are also considered spells in the Creation Kit. Supports backwards discovery.
"formID": "String" // FormID of the spell to learn.
},
{
"type": "ShoutLearned", // Triggers when the player learns a specific number of words of a shout. Supports backwards discovery.
"formID": "String", // FormID of the shout
"words": "Integer" // Number of words required for the achievement to unlock.
},
{
"type": "QueryStatValue", // Triggers when the Tracked Stat event is triggered by the game (eg. Location Discover). Supports backwards discovery. All tracked stats can be found here: https://ck.uesp.net/w/index.php?title=QueryStat_-_Game&mobileaction=toggle_view_desktop
"stat": "String", // Name of the stat
"value": "Integer" // Minimum value
},
{
"type": "GlobalVariableState", // Since Skyrim doesn't trigger any event for this I check this whenever data is loaded (area change, game loaded, save loaded, etc...). Supports backwards discovery.
"formID": "String", // FormID of the variable.
"value": "Float" // Float value of the variable (if it's short still use decimals, eg. 4.0 instead of 4
},
{
"type": "AchievementUnlocked", // This event gets triggered when an achievement is unlocked. Useful if you want a "unlock all achievements" condition. Supports backwards discovery.
"achievement": "String", // Name of the achievement to unlock
"group": "String" // (Optional) Name of the json file containing the achievement. It defaults to the current json file, so if you want to refer to your achievements you can leave it blank.
}
Keep in mind I'm always looking to add more events, and during the alpha my intention is to finish all the game achievements and then start adding additional events and user-requested events. The properties name might change to make them more uniform, but I will try to keep compatibility via a version parameter after the 1.0 release.
With these informations and the examples that you can find in the code advanced users will surely be able to create your own achievements.
If, instead, you still feel confused, you can head over to the Examples section of the wiki where I guide you step-to-step on how to write your first achievements.
For any other question you can contact me either on github, Nexusmods (ItsNicklaj) or Discord (@Nicklaj).
0 comments